I just started to play around with MongoDB (C#) and tried to port a repository over from entity framework. I'm using the official C# driver 1.0. Now I did something like this:
internal class MongoContext
{
public MongoContext(string constring)
{
MongoServer server = MongoServer.Create(constring);
this.myDB = server.GetDatabase("MyDB");
BsonClassMap.RegisterClassMap<VoyageNumber>(cm =>
{ cm.MapField<string>(p => p.Id); });
BsonClassMap.RegisterClassMap<Schedule>(cm =>
{ cm.MapField<DateTime>(p => p.EndDate); cm.MapField<DateTime>(p => p.StartDate); });
BsonClassMap.RegisterClassMap<Voyage>(cm =>
{ cm.MapIdField<VoyageNumber>(p => p.VoyageNumber); cm.MapField<Schedule>(p => p.Schedule); });
}
private MongoDatabase myDB;
public MongoDatabase MyDB
{ get { return this.myDB; } }
}
I'd then go on and implement the Repository like this:
public class MongoVoyageRepository : IVoyageRepository
{
private readonly MongoContext context;
public MongoVoyageRepository(string constring)
{
this.context = new MongoContext(constring);
}
public void Store(Domain.Model.Voyages.Voyage voyage)
{
MongoCollection<Voyage> mongoVoyages = context.MyDB.GetCollection<Voyage>("Voyages");
//store logic...
}
}
Now I'd like to know if it is a good decision to instantiate a "context" like this in terms of performance. Does it make sense to put the BsonClass Maps in there?
Thank you for your input.
// entity base
public class MongoEntity {
public ObjectId _id { get; set; }
}
//user entity
public class Users : MongoEntity {
public string UserName { get; set; }
public string Password { get; set; }
}
// simple repository
public class Repository {
private MongoDatabase _db;
public MongoDatabase Database { get; private set; }
public Repository(string constr, string dbname) {
var server = MongoServer.Create(constr);
_db = server.GetDatabase(dbname);
Database = _db;
}
private MongoCollection<T> GetCollection<T>() where T : MongoEntity {
return _db.GetCollection<T>(typeof(T).Name);
}
public IEnumerable<T> List<T>() where T : MongoEntity {
return GetCollection<T>().FindAll();
}
public IEnumerable<T> List<T>(Expression<Func<T, bool>> exp) where T : MongoEntity {
return GetCollection<T>().AsQueryable<T>().Where(exp);
}
public T Single<T>(Expression<Func<T, bool>> exp) where T : MongoEntity {
return List<T>(exp).SingleOrDefault();
}
public void Insert<T>(T entity) where T : MongoEntity {
GetCollection<T>().Insert<T>(entity);
}
public void Insert<T>(ICollection<T> entities) where T : MongoEntity {
GetCollection<T>().InsertBatch(entities);
}
// Update, Delete method etc ...
}
// example
var repository = new Repository("mongodb://localhost", "test");
repository.Single<Users>(u => u.UserName == "myUserName");
I guess it does not make sense to register classes mapping each time when you create your repository class. Since MongoDB C# driver manages connections to the MongoDB internally, it seems to me that it's better to create MongoServer and register classes mapping only once, during application start and then use it.
I am using singleton in order to create MongoServer only once
public class MongoRead : MongoBase
{
public MongoRead(MongoServer server)
: base(server)
{
}
public override MongoDatabase Database
{
get { return Server.GetDatabase("myDb"); }
}
public MongoCollection Logs
{
get { return Database.GetCollection("logs"); }
}
private static MongoRead _instance = null;
public static MongoRead Instance
{
get
{
if (_instance == null)
{
_instance = RegisterMongoDb();
}
return _instance;
}
}
private static MongoRead RegisterMongoDb()
{
var readServer = MongoServer.Create(connectionString);
var read = new MongoRead(readServer);
var myConventions = new ConventionProfile();
myConventions.SetIdMemberConvention(new NoDefaultPropertyIdConvention());
BsonClassMap.RegisterConventions(myConventions, t => true);
return read;
}
}
So you also can use above class in your Repository:
public class MongoVoyageRepository : IVoyageRepository
{
private readonly MongoRead context
{
get { return MongoRead.Instance; }
};
public MongoVoyageRepository()
{
}
public void Store(Domain.Model.Voyages.Voyage voyage)
{
MongoCollection<Voyage> mongoVoyages =
context.Database.GetCollection<Voyage>("Voyages");
//store logic...
}
}
this is also interesting if you want to use repository pattern.
Related
I am having issues with hydrating a class which consists of a composite ID which in turn has a base class, I am getting an error saying InvalidOperationException: {document}.Identity is not supported.
The class i am trying to write to the database is below:
public class Product : IEntity<Product>
{
public readonly Sku Sku;
public string Name { get; private set; }
public string Description { get; private set; }
public bool IsArchived { get; private set; }
public Identity<Product> Identity => Sku;
public Product(Sku sku, string name, bool isArchived)
{
Sku = sku;
Name = name;
IsArchived = isArchived;
}
}
public interface IEntity<T>
{
Identity<T> Identity { get; }
}
In turn has an ID Sku which is a class formed of the below composite values (VendorId and a local Value within Sku):
public class Sku : Identity<Product>
{
public readonly VendorId VendorId;
public readonly string Value;
public Sku(VendorId vendorId, string value)
{
VendorId = vendorId;
Value = value;
}
protected override IEnumerable<object> GetIdentityComponents()
{
return new object[] {VendorId, Value};
}
}
public class VendorId : Identity<Vendor>
{
public readonly string Value;
public VendorId(string value)
{
Value = value;
}
protected override IEnumerable<object> GetIdentityComponents()
{
return new object[] {Value};
}
}
I have a base class for my entities Identity which i use in my DDD libraries, essentially the ToString() output here could be used as the ID if this would simplify things:
public abstract class Identity<T> : IEquatable<Identity<T>>
{
public override bool Equals(object obj) { /* snip */ }
public bool Equals(Identity<T> other) { /* snip */ }
public override int GetHashCode() { /* snip */ }
public override string ToString()
{
var id = string.Empty;
foreach (var component in GetIdentityComponents())
{
if (string.IsNullOrEmpty(id))
id = component.ToString(); // first item, dont add a divider
else
id += "." + component;
}
return id;
}
protected abstract IEnumerable<object> GetIdentityComponents();
}
I register the mappings on app start:
// rehydrate readonly properties via matched constructor
// https://stackoverflow.com/questions/39604820/serialize-get-only-properties-on-mongodb
ConventionRegistry
.Register(nameof(ImmutablePocoConvention), new ConventionPack { new ImmutablePocoConvention() }, _ => true);
BsonClassMap.RegisterClassMap<Product>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Sku);
});
BsonClassMap.RegisterClassMap<Vendor>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id);
});
However when i go and write, i get InvalidOperationException: {document}.Identity is not supported.
// my respositoru method
public void Upsert<T>(T entity) where T : IEntity<T>
{
this.Database
.GetCollection<T>(product.GetType().FullName)()
.ReplaceOneAsync(x=>x.Identity.Equals(entity.Identity), entity, new UpdateOptions() {IsUpsert = true})
.Wait();
}
var product = new Product(new Sku(new VendorId("dell"), "12434" ),"RAM", false );
myProductRepo.Upsert(product);
Not sure if this is now overly complicated by me persisting direct from my entities layer (or if i just use an automapper and simpler POCO)... or if I am missing some mapping directives.
Appreciate any help or pointers.
I was looking at the hydration via constructor post which is done through GetProperties.
So public readonly Sku Sku; doesn't show up through classMap.ClassType.GetTypeInfo().GetProperties(_bindingFlags) because it is only can be accessed as member field.
You can change it to public Sku Sku { get; } so it is hydrated through constructor via GetProperties and change all the readonly fields (Sku - VendorId, Value & VendorId - Value fields) to have property getter method.
Also, You've to add cm.MapProperty(c => c.Identity) so x=>x.Identity.Equals(entity.Identity) can be serialized when used as expression because Identity cannot be hydrated and registered through ImmutablePocoConventionas it is not a constructor arg when automap logic runs.
Code changes:
public class Sku : Identity<Product>
{
public VendorId VendorId { get; }
public string Value { get; }
}
public class VendorId : Identity<Vendor>
{
public string Value { get; }
}
BsonClassMap.RegisterClassMap<Product>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Sku);
cm.MapProperty(c => c.Identity);
});
Here is the code i used:
public class ProductMongoRepository : IProductRepository
{
public ICollection<Product> SearchBySkuValue(string sku)
{
return ProductsMongoDatabase.Instance.GetEntityList<Product>();
}
public Product GetBySku(Sku sku)
{
var collection = ProductsMongoDatabase.Instance.GetCollection<Product>();
return collection.Find(x => x.Sku.Equals(sku)).First();
}
public void SaveAll(IEnumerable<Product> products)
{
foreach (var product in products)
{
Save(product);
}
}
public void Save(Product product)
{
var collection = ProductsMongoDatabase.Instance.GetCollection<Product>();
collection
.ReplaceOneAsync(
x => x.Sku.Equals(product.Sku),
product,
new UpdateOptions() { IsUpsert = true })
.Wait();
}
}
Setting up the mapping here and support for readonly fields via constructor, for more complex scenarios and manual POCO mapping we could use BsonSerializer.RegisterSerializer(typeof(DomainEntityClass), new CustomerSerializer());
public sealed class ProductsMongoDatabase : MongoDatabase
{
private static volatile ProductsMongoDatabase instance;
private static readonly object SyncRoot = new Object();
private ProductsMongoDatabase()
{
BsonClassMap.RegisterClassMap<Sku>(cm =>
{
cm.MapField(c => c.VendorId);
cm.MapField(c => c.SkuValue);
cm.MapCreator(c => new Sku(new VendorId(c.VendorId.VendorShortname), c.SkuValue));
});
BsonClassMap.RegisterClassMap<VendorId>(cm =>
{
cm.MapField(c => c.VendorShortname);
cm.MapCreator(c => new VendorId(c.VendorShortname));
});
BsonClassMap.RegisterClassMap<Product>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Sku);
cm.MapCreator(c => new Product(c.Sku, c.Name, c.IsArchived));
});
BsonClassMap.RegisterClassMap<Vendor>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id);
cm.MapCreator(c => new Vendor(c.Id, c.Name));
});
}
public static ProductsMongoDatabase Instance
{
get
{
if (instance != null)
return instance;
lock (SyncRoot)
{
if (instance == null)
instance = new ProductsMongoDatabase();
}
return instance;
}
}
}
The above implementation (which is a singleton) derives from the below base (any queries or writes are done in the parent implementation):
public abstract class MongoDatabase
{
private readonly IConfigurationRepository _configuration;
private readonly IMongoClient Client;
private readonly IMongoDatabase Database;
protected MongoDatabase()
{
//_configuration = configuration;
var connection = "mongodb://host:27017";
var database = "test";
this.Client = new MongoClient();
this.Database = this.Client.GetDatabase(database);
}
public List<T> GetEntityList<T>()
{
return GetCollection<T>()
.Find(new BsonDocument()).ToList<T>();
}
public IMongoCollection<T> GetCollection<T>()
{
return this.Database.GetCollection<T>(typeof(T).FullName);
}
}
My Sku domain model:
public class Sku : Identity<Product>
{
public readonly VendorId VendorId;
public readonly string SkuValue;
public Sku(VendorId vendorId, string skuValue)
{
VendorId = vendorId;
SkuValue = skuValue;
}
protected override IEnumerable<object> GetIdentityComponents()
{
return new object[] {VendorId, SkuValue};
}
}
My Product domain model:
public class Product : IEntity<Product>
{
public readonly Sku Sku;
public string Name { get; private set; }
public bool IsArchived { get; private set; }
public Product(Sku sku, string name, bool isArchived)
{
Sku = sku;
Name = name;
IsArchived = isArchived;
}
public void UpdateName(string name)
{
Name = name;
}
public void UpdateDescription(string description)
{
Description = description;
}
public void Archive()
{
IsArchived = true;
}
public void Restore()
{
IsArchived = false;
}
// this is used by my framework, not MongoDB
public Identity<Product> Identity => Sku;
}
My VendorID:
public class VendorId : Identity<Vendor>
{
public readonly string VendorShortname;
public VendorId(string vendorShortname)
{
VendorShortname = vendorShortname;
}
protected override IEnumerable<object> GetIdentityComponents()
{
return new object[] {VendorShortname};
}
}
Then i have my entity and identity types:
public interface IEntity<T>
{
Identity<T> Identity { get; }
}
public abstract class Identity<T> : IEquatable<Identity<T>>
{
private const string IdentityComponentDivider = ".";
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj)) return true;
if (ReferenceEquals(null, obj)) return false;
if (GetType() != obj.GetType()) return false;
var other = obj as Identity<T>;
return other != null && GetIdentityComponents().SequenceEqual(other.GetIdentityComponents());
}
public override string ToString()
{
var id = string.Empty;
foreach (var component in GetIdentityComponents())
{
if (string.IsNullOrEmpty(id))
id = component.ToString(); // first item, dont add a divider
else
id += IdentityComponentDivider + component;
}
return id;
}
protected abstract IEnumerable<object> GetIdentityComponents();
public override int GetHashCode()
{
return HashCodeHelper.CombineHashCodes(GetIdentityComponents());
}
public bool Equals(Identity<T> other)
{
return Equals(other as object);
}
}
Could you please advice me which of these two (if any) approaches to Transaction Script patter is correct?
Basically I need to implement "pure" TS, which is not to bend it in some convenient way. And the question is when I get data from Table Data Gateway shall I store them, for different parts (logical part for sake of clarity) of transaction and use them where do I need, OR get data don't store them but directly use them and if the same data are needed again call table gateway and ask for it.
Example: http://pastebin.com/hGCgrfEs
Thanks. Hopefully it makes sense if not let me know :)
The TSDoSomething2 class is closer to a good solution, i propose this to implement a SOLID transcript implementation:
public class OrderBusiness : IAcceptVisitOrder
{
private readonly string _id;
private int _status;
public OrderBusiness(string id, int status)
{
_id = id;
_status = status;
}
public void ChangeStatus(int newStatus)
{
//validation logic....
_status = newStatus;
}
public void Accept(IOrderVisitor visitor)
{
if (visitor == null) throw new ArgumentNullException(nameof(visitor));
visitor.Visit(_id, _status);
}
}
public class Client
{
private readonly OrderApplicationService _service;
//Get from Service locator like ninject, etc
private readonly IReader<OrderBusiness, string> _reader = new OrderBusinessReader();
private readonly IOrderVisitor _visitor = new UpdateOrderVisitor();
public Client()
{
_service = new OrderApplicationService(_reader, _visitor);
}
public void Run(UpdateOrderCommand command)
{
_service.When(command);
}
}
public class UpdateOrderCommand
{
[Required]
public string Id { get; set; }
[Required]
public int Status { get; set; }
}
public class OrderApplicationService
{
private readonly IReader<OrderBusiness, string> _reader;
private readonly IOrderVisitor _visitor;
public OrderApplicationService(IReader<OrderBusiness, string> reader, IOrderVisitor visitor)
{
if (reader == null) throw new ArgumentNullException(nameof(reader));
if (visitor == null) throw new ArgumentNullException(nameof(visitor));
_reader = reader;
_visitor = visitor;
}
public void When(UpdateOrderCommand command)
{
var order = GetBusinessObject(command.Id);
order.ChangeStatus(command.Status);
order.Accept(_visitor);
}
private OrderBusiness GetBusinessObject(string id)
{
if (id == null) throw new ArgumentNullException(nameof(id));
return _reader.Read(id);
}
}
public interface IReader<out T, in TK>
{
T Read(TK id);
}
public interface IAcceptVisitOrder
{
void Accept(IOrderVisitor visitor);
}
public interface IOrderVisitor
{
void Visit(string id, int status);
}
public class OrderBusinessReader : IReader<OrderBusiness, string>
{
public OrderBusiness Read(string id)
{
//Read data from db with EF, ADO.NET, ETC
var status = 0;
return new OrderBusiness(id, status);
}
}
class UpdateOrderVisitor : IOrderVisitor
{
public void Visit(string id, int status)
{
//Persist to database, etc
}
}
Hope this helps!Regards!
I'am working on a MVC solution, my DAL layer I could solve with a Repository classes, everything is working great.
But in my BLL layer I have repetitive code:
My Crud is the same, my fields and consructor are different.
I can also have some extra methodes.
Is there a way to solve this on a proper way?
Class 1
public class JobTypeLogic
{
#region Fields
public JobType JobType { get; set; }
private UnitOfWork unitOfWork = new UnitOfWork();
public Repository<JobType> JobTypeEngine { get; set; }
#endregion
#region Constructor
public JobTypeLogic()
{
JobType = new JobType();
JobTypeEngine = unitOfWork.Repository<JobType>();
}
#endregion
#region CRUD
public void Add()
{
JobTypeEngine.Add(JobType);
}
public JobType Get(long id)
{
return JobType = JobTypeEngine.Get(id);
}
public void Edit()
{
JobTypeEngine.Edit(JobType);
}
public void Delete()
{
JobTypeEngine.Delete(JobType);
}
public List<JobType> List()
{
return JobTypeEngine.List.ToList();
}
#endregion
}
Class 2
public class JobLogic
{
#region Fields
public Job Job { get; set; }
public IEnumerable<SelectListItem> JobTypeList { get; set; }
private UnitOfWork unitOfWork = new UnitOfWork();
public Repository<Job> JobEngine;
private Repository<JobType> JobTypeEngine;
#endregion
#region Constructor
public JobLogic()
{
Job = new Job();
JobEngine = unitOfWork.Repository<Job>();
JobTypeEngine = unitOfWork.Repository<JobType>();
JobTypeList = GetJobTypeList();
}
#endregion
#region CRUD
public void Add()
{
JobEngine.Add(Job);
}
public Job Get(long id)
{
return Job = JobEngine.Get(id);
}
public void Edit()
{
JobEngine.Edit(Job);
}
public void Delete()
{
JobEngine.Delete(Job);
}
public List<Job> List()
{
return JobEngine.List.ToList();
}
#endregion
#region Methode
private IEnumerable<SelectListItem> GetJobTypeList()
{
JobTypeEngine = unitOfWork.Repository<JobType>();
var jobs = JobTypeEngine.List
.Select(x =>
new SelectListItem
{
Value = x.ID.ToString(),
Text = x.Name
});
return new SelectList(jobs, "Value", "Text");
}
#endregion
}
You could create a generic base class
public class GenericJobLogic<T> where T : IJob
{
private Repository<T> engine;
public GenericJobLogic()
{
this.engine = unitOfWork.Repository<T>();
}
public virtual T Get(long id)
{
return this.engine.Get(id);
}
}
This assumes Job and JobType both implement IJob or some other base class JobBase. Or you could always just do where T : class.
Usage becomes
var jobBll = new GenericJobLogic<Job>();
Job job = jobBll.Get(1);
You can still override your base BLL class. Then override or extend only the necessary parts instead of writing a full implementation.
public class JobLogic : GenericJobLogic<Job>
{
public override Job Get(long id) { }
public IEnumerable<JobType> GetJobTypeList() { }
}
Thanks for the response.
I solved the problem by creating a generic base class and inherited from that class.
Base class
public class GenericLogic<T> where T : BaseEntity
{
private Repository<T> engine;
private UnitOfWork unitOfWork = new UnitOfWork();
public T Entity;
public GenericLogic()
{
this.engine = unitOfWork.Repository<T>();
}
#region CRUD
public void Add()
{
engine.Add(Entity);
}
public T Get(long id)
{}
public void Edit()
{}
public void Delete()
{}
public List<T> List()
{}
#endregion
}
The two BLL classes (and the rest of the BLL) become lighter and not repetitive.
BLL class
public class JobLogic : GenericLogic<Job>
{
#region Fields
public Job Job { get; set; }
public IEnumerable<SelectListItem> JobTypeList { get; set; }
#endregion
#region Constructor
public JobLogic()
{
Job = new Job();
JobTypeList = GetJobTypeList();
}
#endregion
#region Methode
private IEnumerable<SelectListItem> GetJobTypeList()
{
UnitOfWork unitOfWork = new UnitOfWork();
Repository<JobType> jobTypeEngine = unitOfWork.Repository<JobType>();
var jobs = jobTypeEngine.List
.Select(x =>
new SelectListItem
{
Value = x.ID.ToString(),
Text = x.Name
});
return new SelectList(jobs, "Value", "Text");
}
#endregion
}
DAL classes is
public class Repository<T> where T : BaseEntity
{
private readonly FlowContext context;
private IDbSet<T> entities;
string errorMessage = string.Empty;
public Repository(FlowContext context)
{
this.context = context;
}
public T Get(object id)
{}
public void Add(T entity)
{}
public void Edit(T entity)
{}
public void Delete(T entity)
{}
private IDbSet<T> Entities
{}
}
The only problem I have with this solution is that I can't use the Entity field from my base class in MVC. Therefore I created a Field Job. This has todo with the binding between the controller and the HTML page.
Hopefully is this code a great help for other people.
Let's assume that I have this scenario: I have got 2 repositories of information, and I want to access both, but it would be nice to leave the task of deciding which repo to use to common class.
The goal is to accomplish this with something similar to the code I've wrote below, but this sounds pretty bad:
where TOnline : class
where TOffline : class
where TContract : class
Sure I can ommit that, but bassically what I'm asking is what to do in order to stop using reflection and go typed. Maybe any design-pattern recomendation?
Code (if you copy/paste this on a console app replacing the Program class you should be able to run the example)
using CustomerDispatcher = DispatcherProxy<CustomerOnline, CustomerOffline, ICustomer>;
public interface ICustomer
{
string Get(int id);
}
public class CustomerOnline : ICustomer
{
public string Get(int id)
{
// Get From intranet DB
return "From DB";
}
}
public class CustomerOffline : ICustomer
{
public string Get(int id)
{
// Get From local storage
return "From local storage";
}
}
public class DispatcherProxy<TOnline, TOffline, TContract>
where TOnline : class
where TOffline : class
where TContract : class
{
public TContract Instance { get; set; }
public bool IsConnected { get; set; }
public DispatcherProxy()
{
// Asume that I check if it's connected or not
if (this.IsConnected)
this.Instance = (TContract)Activator.CreateInstance(typeof(TOnline));
else
this.Instance = (TContract)Activator.CreateInstance(typeof(TOffline));
}
}
class Program
{
static void Main(string[] args)
{
var customerDispatcher = new CustomerDispatcher();
Console.WriteLine("Result: " + customerDispatcher.Instance.Get(1));
Console.Read();
}
}
Thanks in advance!
You can add the new() constraint:
public class DispatcherProxy<TOnline, TOffline, TContract>
where TOnline : class, new()
where TOffline : class, new()
where TContract : class //isn't TContract an interface?
{
public TContract Instance { get; set; }
public bool IsConnected { get; set; }
public DispatcherProxy()
{
// Asume that I check if it's connected or not
if (this.IsConnected)
this.Instance = new TOnline() as TContract;
else
this.Instance = new TOffline() as TContract;
}
}
In case any of you are interested, I had to change the way I did this because it was checking connection at Constructor Level, and I needed that check at Operation Level.
using System;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace ConsoleApplication1
{
public enum ConnectionStatus
{
Online,
Offline,
System // System checks connectivity
}
public static class Connectivity
{
private static ConnectionStatus ConnectionStatus = ConnectionStatus.Offline;
public static void ForceConnectionStatus(ConnectionStatus connectionStatus)
{
ConnectionStatus = connectionStatus;
}
public static bool IsConnected()
{
switch (ConnectionStatus)
{
case ConnectionStatus.Online:
return true;
case ConnectionStatus.Offline:
return false;
case ConnectionStatus.System:
return CheckConnection();
}
return false;
}
private static bool CheckConnection()
{
return true;
}
}
public class Unity
{
public static IUnityContainer Container;
public static void Initialize()
{
Container = new UnityContainer();
Container.AddNewExtension<Interception>();
Container.RegisterType<ILogger, OnlineLogger>();
Container.Configure<Interception>().SetInterceptorFor<ILogger>(new InterfaceInterceptor());
}
}
class Program
{
static void Main(string[] args)
{
Unity.Initialize();
var r = new Router<ILogger, OnlineLogger, OnlineLogger>();
Connectivity.ForceConnectionStatus(ConnectionStatus.Offline);
Console.WriteLine("Calling Online, will attend offline: ");
r.Logger.Write("Used offline.");
Connectivity.ForceConnectionStatus(ConnectionStatus.Online);
Console.WriteLine("Calling Online, will attend online: ");
r.Logger.Write("Used Online. Clap Clap Clap.");
Console.ReadKey();
}
}
public class Router<TContract, TOnline, TOffline>
where TOnline : TContract
where TOffline : TContract
{
public TContract Logger;
public Router()
{
Logger = Unity.Container.Resolve<TContract>();
}
}
public interface IOnline
{
IOffline Offline { get; set; }
}
public interface IOffline
{
}
public interface ILogger
{
[Test()]
void Write(string message);
}
public class OnlineLogger : ILogger, IOnline
{
public IOffline Offline { get; set; }
public OnlineLogger()
{
this.Offline = new OfflineLogger();
}
public void Write(string message)
{
Console.WriteLine("Online Logger: " + message);
}
}
public class OfflineLogger : ILogger, IOffline
{
public IOnline Online { get; set; }
public void Write(string message)
{
Console.WriteLine("Offline Logger: " + message);
}
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
public class TestAttribute : HandlerAttribute
{
public override ICallHandler CreateHandler(IUnityContainer container)
{
return new TestHandler();
}
}
public class TestHandler : ICallHandler
{
public int Order { get; set; }
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
Console.WriteLine("It's been intercepted.");
if (!Connectivity.IsConnected() && input.Target is IOnline)
{
Console.WriteLine("It's been canceled.");
var offline = ((input.Target as IOnline).Offline);
if (offline == null)
throw new Exception("Online class did not initialized Offline Dispatcher.");
var offlineResult = input.MethodBase.Invoke(offline, this.GetObjects(input.Inputs));
return input.CreateMethodReturn(offlineResult, this.GetObjects(input.Inputs));
}
return getNext()(input, getNext);
}
private object[] GetObjects(IParameterCollection parameterCollection)
{
var parameters = new object[parameterCollection.Count];
int i = 0;
foreach (var parameter in parameterCollection)
{
parameters[i] = parameter;
i++;
}
return parameters;
}
}
}
I have the following C# code. Here the validations are kept outside the class to satisfy Open – Closed Principle. This is working fine. But the challenge is – the validations are not generic. It is specific to employee class (E.g DateOfBirthRuleForEmployee). How do I make the validations generic for all objects (DateOfBirthRuleForAnyObject).
Note: Make Generic <==> Make Type-Independent
Note: I have NameLengthRuleForEmployee validation also. New validation may come in future.
EDIT
Generic Method Example: Using “OfType” in LINQ
CODE
class Program
{
static void Main(string[] args)
{
Employee employee = new Employee();
employee.DateOfBirth = DateTime.Now;
employee.Name = "Lijo";
DateOfBirthRuleForEmployee dobRule = new
DateOfBirthRuleForEmployee();
NameLengthRuleForEmployee nameRule = new
NameLengthRuleForEmployee();
EmployeeManager employeeManager = new EmployeeManager();
employeeManager.AddRules(dobRule);
employeeManager.AddRules(nameRule);
bool result = employeeManager.validateEntity(employee);
Console.WriteLine(result);
Console.ReadLine();
}
}
public interface IEntity
{
}
public interface IRule<TEntity>
{
bool IsValid(TEntity entity);
}
public class DateOfBirthRuleForEmployee : IRule<Employee>
{
public bool IsValid(Employee entity)
{
return (entity.DateOfBirth.Year <= 1975);
}
}
public class NameLengthRuleForEmployee : IRule<Employee>
{
public bool IsValid(Employee employee)
{
return (employee.Name.Length < 5);
}
}
public class Employee : IEntity
{
private DateTime dateOfBirth;
private string name;
public DateTime DateOfBirth
{
get
{
return dateOfBirth;
}
set
{
dateOfBirth = value;
}
}
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
}
public class EmployeeManager
{
RulesEngine<Employee> engine = new RulesEngine<Employee>();
public void AddRules(IRule<Employee> rule)
{
engine.AddRules(rule);
//engine.AddRules(new NameLengthRuleForEmployee());
}
public bool validateEntity(Employee employee)
{
List<IRule<Employee>> rulesList = engine.GetRulesList();
//No need for type checking. Overcame Invariance problem
bool status = true;
foreach (IRule<Employee> theRule in rulesList)
{
if (!theRule.IsValid(employee))
{
status = false;
break;
}
}
return status;
}
}
public class RulesEngine<TEntity> where TEntity : IEntity
{
private List<IRule<TEntity>> ruleList = new
List<IRule<TEntity>>();
public void AddRules(IRule<TEntity> rule)
{
//invariance is the key term
ruleList.Add(rule);
}
public List<IRule<TEntity>> GetRulesList()
{
return ruleList;
}
}
The challange is for your rules to know which property of what type to validate. You can either provide this by implementing an interface that provides just that as suggested by SLaks or by quessing it dynamically or by providing a concrete rule class with a bit more information on how to access the given property, e.g.:
class NameRule<T> : IRule<T>
{
private Func<T, string> _nameAccessor;
public NameRule(Func<T, string> nameAccessor)
{
_nameAccessor = nameAccessor;
}
public bool IsValid(T instance)
{
return _nameAccessor(instance).Length > 10;
}
}
this ofcourse can be used in the following way:
NameRule<Employee> employeeNameRule = new NameRule<Employee>(x => x.name);
employeeManager.addRule(employeeNameRule);