I have below code I am working on. I am working on data access adon.net layer. I have problem with my business layer class called UserBAL. The problem is that I am creating instance of dal and dbmanager inside constructor of UserBAL. How can I change this to be loosely coupled for UserBAL? Hope you get my point.
public interface IEntity
{
int Id { get; set; }
int DoSomething(string one, int two);
}
public class User : IEntity
{
public int Id { get; set; }
public int DoSomething(string one, int two)
{
throw new NotImplementedException();
}
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
public class UserBal //busines logic
{
private readonly IRepositoryDal<User> _userRepositoryDal;
public UserBal()
{
_userRepositoryDal = new UserRepositoryDal(new DbManager("sqlserver?"));
}
public IEnumerable<User> SearchByName(string name)
{
return _userRepositoryDal.SearchByName(name);
}
}
interface IRepositoryDal<T> where T : IEntity
{
IEnumerable<T> SearchByName(string username);
T SearchById(string id);
void Update(T entity);
void Remove(T entity);
void Add(T entity);
}
public class UserRepositoryDal: IRepositoryDal<User>
{
private readonly IDbManager _dbManager;
public UserRepositoryDal(IDbManager dbManager)
{
//read from either singleton or configuration file !!
_dbManager = dbManager;
}
public IEnumerable<User> SearchByName(string username)
{
var parameters = new List<IDbDataParameter>
{
_dbManager.CreateParameter("#FirstName", 50, username, DbType.String),
};
var userDataTable = _dbManager.GetDataTable("storedpr2",
CommandType.StoredProcedure, parameters.ToArray());
foreach (DataRow dr in userDataTable.Rows)
{
var user = new User
{
Id = int.Parse(dr["Id"].ToString()),
Firstname = dr["Firstname"].ToString(),
Lastname = dr["LastName"].ToString(),
Email = dr["Email"].ToString()
};
yield return user;
}
}
public User SearchById(string id)
{
var parameters = new List<IDbDataParameter>
{
_dbManager.CreateParameter("#Id", 50, id, DbType.Int32),
};
var userDataTable = _dbManager.GetDataTable("storedpr2",
CommandType.StoredProcedure, parameters.ToArray());
return new User
{
Id = int.Parse(userDataTable.Rows[0]["Id"].ToString()),
Firstname = userDataTable.Rows[0]["Firstname"].ToString(),
Lastname = userDataTable.Rows[0]["LastName"].ToString(),
Email = userDataTable.Rows[0]["Email"].ToString()
};
}
public void Update(User entity)
{
throw new System.NotImplementedException();
}
public void Remove(User entity)
{
throw new System.NotImplementedException();
}
public void Add(User entity)
{
throw new System.NotImplementedException();
}
}
public partial class FrmLogin : Form
{
private readonly UserBal _userBal;
public FrmLogin()
{
InitializeComponent();
_userBal = new UserBal();
}
}
You should use dependency injection, and for required dependencies, you can use constructor injection, e.g:
public class UserBal
{
private readonly IRepositoryDal<User> _userRepositoryDal;
public UserBal(IRepositoryDal<User> userRepositoryDal)
{
_userRepositoryDal = userRepositoryDal
?? throw new ArgumentNullException(nameof(userRepositoryDal));
}
...
}
Dependency injection is the way to go. Here's a simplified example of your situation.
Given your classes could be like this:
public interface IEntity { }
public interface IRepositoryDal<T> where T : IEntity { }
public interface IDbManager { }
public class User : IEntity { }
public class UserBal //busines logic
{
[Injectivity.Attributes.Inject]
private IRepositoryDal<User> _userRepositoryDal;
}
public class UserRepositoryDal: IRepositoryDal<User>
{
[Injectivity.Attributes.Inject]
private IDbManager _dbManager;
}
public class DbManager : IDbManager
{
[Injectivity.Attributes.Construct()]
public DbManager([Injectivity.Attributes.Key("dbKey", typeof(string))] string x)
{
Console.WriteLine($"DbManager created with parameter \"{x}\"");
}
}
...then this code:
var context = Injectivity.Context.CreateRoot();
context.SetConfig<string>("dbKey", "sqlserver?");
context.SetFactory<IDbManager, DbManager>();
context.SetFactory<IRepositoryDal<User>, UserRepositoryDal>();
context.SetFactory<UserBal, UserBal>();
var user = context.Resolve<UserBal>();
...will produce this output:
DbManager created with parameter "sqlserver?"
Now, I've used a DI container that I wrote a number of years back. It's very flexible.
So the call to context.Resolve<UserBal>(); runs down the chain of seeing what needs to be injected to create all of the instances of all of the objects. Ultimately the creation of UserBal requires the DbManager to be created and hence the output.
You would normally not explicitly register each factory. Normally you would put attributes on all of the classes you want to register and then use context.Register(Assembly.LoadFrom("My.DLL")); or create an XML config file and call context.LoadConfig(XDocument.Load("config.xml"));.
You can even do things like this:
context.SetDecorator<IRepositoryDal<User>, UserRepositoryDalDecorator>();
This will cause all calls to context.Resolve<IRepositoryDal<User>>() or [Inject] attributes to automatically wrap the real instances in this decorator. Ideal to intercepting method calls for debugging purposes.
Related
Is it possible to expose class public properties in different class through IOC. I am creating an instance of Interface but i am not able to access public properties of class. I am using Unity.WebApi for resolving dependencies.
TransactionService Class
public class TransactionService : ITransactionService
{
private readonly IMRepository _mRepository;
private readonly IFService _fGateway;
public TransactionService(IMbaRepository mbaRepository, IFpnService fpnService)
{
_mRepository = mRepository;
_fGateway = fService;
}
private List<Transaction> SearchTransacionsByUser(FUser objFUser)
{
foreach (var item in something)
{
//can't use _fGateway to set properties because Interface
// don't implement them
_fGateway.OID = objFUser.OID.ToString();
_fGateway.Amount = objFUser.Amount;
_fGateway.Search(criteria);
}
}
}
FService class
public class FService : IFpService
{
public string _OID { get; set; }
public decimal _Amount{ get; set; }
public TransactionResponse Search(string criteria)
{
TransactionOperationInput _input;
_input = new TransactionOperationInput()
{
Criteria = _criteria,
OID = _OID,
Amount = _Amount
};
// search transactions
}
}
If you are in control of the services then refactor the interfaces to expose the desired members
public interface IFService {
TransactionResponse Search(TransactionOperationInput input);
}
Make sure the derived implementation has those members
public class FService : IFpService {
public TransactionResponse Search(TransactionOperationInput input) {
// search transactions
}
}
And that the dependent class uses the correct abstraction
public class TransactionService : ITransactionService {
private readonly IMRepository _mRepository;
private readonly IFService fGateway;
public TransactionService(IMbaRepository mbaRepository, IFService fService) {
_mRepository = mRepository;
fGateway = fService;
}
private List<Transaction> SearchTransacionsByUser(FUser objFUser) {
foreach (var item in something) {
TransactionOperationInput input = new TransactionOperationInput() {
Criteria = _criteria,
OID = objFUser.OID.ToString(),
Amount = objFUser.Amount,
};
fGateway.Search(input);
//...
}
//...
}
}
Finally make sure the register the appropriate abstractions and implementations with the IoC/DI container.
Environment:
I am working in Webapi. There is 2 entity classes which are follows;
public class Class1
{
public Class1()
{
this.items = new HashSet<Class2>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Class2> items { get; set; }
}
public class Class2
{
public int Id { get; set; }
public string Name { get; set; }
public int Class1Id { get; set; }
public virtual Class1 class1 { get; set; }
}
Business Layer:
The buniess layer have the following codes;
public class Class1Logic : IClass1Logic
{
private readonly IClass1Repository _repo;
public Class1Logic(IClass1Repository repository)
{
_repo = repository;
}
public async Task<bool> AddClass1ItemAsync(Class1 item)
{
_repo.Add(item);
bool status = await _repo.SaveAsync();
return status;
}
public async Task<Class1> GetClass1ItemAsync(int id)
{
return await _repo.GetAsync(id);
}
}
public class Class2Logic : IClass1Logic
{
private readonly IClass2Repository _repo;
public Class2Logic(IClass2Repository repository)
{
_repo = repository;
}
public async Task<bool> AddClass2ItemAsync(Class2 item)
{
_repo.Add(item);
bool status = await _repo.SaveAsync();
return status;
}
public async Task<Class2> GetClass2ItemAsync(int id)
{
return await _repo.GetAsync(id);
}
}
ViewModels:
public class Class1Model
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Class2Model
{
public int Id { get; internal set; }
public string Name { get; set; }
public int Class1Id { get; set; }
public string Class1Name { get; internal set; }
}
Controllers:
There are 2 contrtollers like Class1Controller and Class2Controller. Both have all CRUD operations.
[RoutePrefix("api/class1items")]
public class Class1Controller : ApiController
{
private readonly IClass1Logic _class1Logic;
private ModelFactory TheFactory;
public Class1Controller(IClass1Logic class1Logic)
{
_class1Logic = class1Logic;
TheFactory = new ModelFactory();
}
[Route("")]
public async Task<IHttpActionResult> Post(Class1Model class1Model)
{
var item = TheFactory.Parse(class1Model);
bool result = await _class1Logic.AddClassItemAsync(item);
if (!result)
{
return BadRequest("Error");
}
string uri = Url.Link("GetLabById", new { id = item.Id });
return Created(uri, TheFactory.Create(item));
}
[Route("{id:int}", Name = "GetClass1ItemById")]
public async Task<IHttpActionResult> GetClass1Item(int id)
{
Class1 item = await _class1Logic.GetClassItemAsync(id);
if (item == null)
{
return NotFound();
}
return Ok(TheFactory.Create(item));
}
}
[RoutePrefix("api/class2items")]
public class Class2Controller : ApiController
{
private readonly IClass2Logic _class2Logic;
private ModelFactory TheFactory;
public Class2Controller(IClass2Logic class2Logic)
{
_class2Logic = class2Logic;
TheFactory = new ModelFactory();
}
[Route("")]
public async Task<IHttpActionResult> Post(Class2Model class2Model)
{
var item = TheFactory.Parse(class2Model);
***//Here item should include Class1 object even if user give ClassId in class2Model***
bool result = await _class2Logic.AddClassItemAsync(item);
if (!result)
{
return BadRequest("Error");
}
string uri = Url.Link("GetClass2ItemById", new { id = item.Id });
return Created(uri, TheFactory.Create(item));
}
}
There is not dependecies in Class1. So all operations are fine. In Class2Controller post method, I got the model object as following to create Class2.
{
"id": 0,
"name": "string",
"class1Id": 1
}
Understanding:
I need to return this viewmodel to user after the create the record. The record created successfully but when mapping to viewmodel i got null exception as Class1 object not in the Class2 object.
In order to get the Class2 object including class1 object, I need to give the class1Object in the request object.
For this i need to find the Class1 object with Class1Id in the request object.
ViewMapper Code:
public class ModelFactory
{
public Class1Model Create(Class1 item)
{
return new Class1Model
{
Id = item.Id,
Name = item.Name
};
}
public Class2Model Create(Class2 item)
{
return new Class2Model
{
Id = item.Id,
Name = item.Name,
Class1Id = item.class1.Id,
Class1Name = item.class1.Name
};
}
public Class1 Parse(Class1Model modelItem)
{
return new Class1
{
Id = modelItem.Id,
Name = modelItem.Name
};
}
public Class2 Parse(Class2Model modelItem)
{
return new Class2
{
Id = modelItem.Id,
Name = modelItem.Name,
Class1Id = modelItem.Class1Id,
***/*Issue Place*/
//class1 = Need to set property by getting object using modelItem.Class1Id***
};
}
}
Issue:
Now i need to call get method of Class1Controller by passing Class1Id.
How to call and is this correct? or my design is bad?
This is initial case. If my Class3 have both Class1 and Class2 again i need to call methods of Class1 and Class2.
Please help to find the correct solution in this case
Note: I added comments the issue area to understand
Well, just to fix this issue you need to manually call _class1Logic.GetClass1ItemAsync after saving. However this doesn't look good.
More elegant ways to fix it:
1) If you always need Class2.Class1 field to be filled use Include when you fetch data (in repository): dbContext.Set<Class2>().Include(c => c.class1).
2) Also you can turn on LazyLoading for EF - I assume it should work in your case.
3) Inject class1Repo to class2Logic and fix up class1 reference after saving - in case if you don't want to enable lazy loading or item was detached from context after save method
Thoughts about design:
I suggest you to look at Automapper or simular libraries instead of ModelFactory where you going to have all mapping logic
Edit: About generic repository: you can modify you GetAsync method
public async Task<T> GetAsync<T>(int id, params Expression<Func<T, object>>[] includes)
where T: class, IEntity
{
var query = context.Set<T>().AsQueryable();
if (includes.Length > 0)
{
query = includes.Aggregate(query,
(current, include) => current.Include(include));
}
return await query.FirstOrDefaultAsync(x => x.Id == id);
}
IEntity interface:
interface IEntity
{
int Id { get; }
}
With this implementation you can use
await _repo.GetAsync<Class2>(id, x => x.class1);
I am new to generics and just wondering if it's possible to avoid the casting in the following code using better OO approach.
public class CollectorFactory
{
public static MyCollector Create(ICredential credential)
{
return new MyCollector(credential);
}
}
public class MyCollector {
public MyCredential Credential { get; set; }
public MyCollector(ICredential credential)
{
this.Credential = (MyCredential)credential;
}
public void Show()
{
Console.WriteLine(this.Credential.Username);
Console.WriteLine(this.Credential.AuthToken);
}
}
public class MyCredential : ICredential
{
public string Username{ get; set; }
public string AuthToken { get; set; }
}
public interface ICredential
{
}
Is there a way to save the casting of ICredential to MyCredential in MyCollector's Constructor? I don't have option to put Username and AuthToken in ICredential as it's implemented by two different Credentials that both have different set of properties. CollectorFactory will be returning different MyCollector instances in the future and both need to have different credentials.
Any help would be really appreciated.
I don't think it's possible given that you're implementing different credentials and trying to use them for ICredential as well.
Here is a way of doing this using generics. Please read my comments in the code.
public class CollectorFactory<T>
{
public T Create(ICredential credential)
{
return (T)Activator.CreateInstance(typeof(T), credential);
}
}
public class MyCollector : BaseCollector
{
public dynamic Credential { get; private set; }
public MyCollector(ICredential credential)
: base(credential)
{
this.Credential = credential;
}
// Having this method here limits your ability to make it more generic.
// Consider moving this to MyCredential since it refers to specific properties in MyCredential.
// If that is not what you want, then you must do a type check before calling methods/ accessing props in Credentials.
public void Show()
{
Console.WriteLine(this.Credential.Username);
Console.WriteLine(this.Credential.AuthToken);
}
}
public class MyCredential : ICredential
{
public string Username { get; set; }
public string AuthToken { get; set; }
}
public abstract class BaseCollector : ICredentialCollector
{
protected BaseCollector(ICredential credential)
{
if (credential == null)
{
throw new ArgumentNullException(nameof(credential));
}
}
}
public interface ICredentialCollector
{
}
public interface ICredential
{
}
// test implementation
public class TestClass
{
public void AuthFactoryTest()
{
// test auth instance
MyCredential auth = new MyCredential() {AuthToken = "asfgasdgdfg", Username = "xuser"};
// Create test factory
var fact = new CollectorFactory<MyCollector>();
var myCollector = fact.Create(auth);
// Do what you need to do to collector object
myCollector.Show();
}
}
Generics isn't the solution in this case. The issue here is that your factory is returning a specific type (MyCollector). A solution around this would be the following:
public class CollectorFactory
{
public static ICollector Create(MyCredential credential)
{
return new MyCollector(credential);
}
public static ICollector Create(OtherCredential credential)
{
return new OtherCollector(credential);
}
}
public interface ICollector
{
void Show();
}
public class MyCollector : ICollector
{
public MyCredential Credential { get; set; }
public MyCollector(MyCredential credential)
{
this.Credential = credential;
}
public void Show()
{
Console.WriteLine(this.Credential.Username);
Console.WriteLine(this.Credential.AuthToken);
}
}
public class MyCredential : ICredential
{
public string Username{ get; set; }
public string AuthToken { get; set; }
}
public interface ICredential
{
}
The above is pretty much the canonical example of the Factory design pattern.
Instead of overloads you could also do typechecking in the factory:
public class CollectorFactory
{
public static ICollector Create(ICredential credential)
{
if(credential.GetType() == typeof(MyCredential))
return new MyCollector((MyCredential) credential);
if(credential.GetType() == typeof(OtherCredential ))
return new OtherCollector((OtherCredential ) credential);
}
}
i having this error as title mention , what causes this anyone?? heres my code
i just want to display the files from SQL Database but it does have error any help? thanks!
namespace a.Models
public interface ICatRepository
{
IEnumerable<Category> GetAll();
Category Get(int id);
Category Add(Category item);
void Remove(int id);
bool Update(Category item);
}
another repository
namespace a.Models
public class CatRepository : ICatRepository
{
private istellarEntities db = new istellarEntities();
public CatRepository()
{
}
public IEnumerable<Category> GetAll()
{
return db.Categories;
}
public Category Get(int id)
{
return db.Categories.Find(id);
}
public Category Add(Category category)
{
db.Categories.Add(category);
db.SaveChanges();
return category;
}
public void Remove(int id)
{
Category category = db.Categories.Find(id);
db.Categories.Remove(category);
db.SaveChanges();
}
public bool Update(Category category)
{
db.Entry(category).State = EntityState.Modified;
db.SaveChanges();
return true;
}
}
controller
namespace a.Controllers
public class APICategoryController : ApiController
{
// static readonly ICatRepository repository = new CatRepository();
private readonly ICatRepository repository;
public APICategoryController(ICatRepository repository)
{
if (repository == null)
{
throw new ArgumentNullException("repository");
}
this.repository = repository;
}
public IEnumerable<Category> GetAllCategories()
{
return repository.GetAll();
}
Lastly my class file
namespace a.Models
using System;
using System.Collections.Generic;
public partial class Category
{
public Category()
{
this.IQuestions = new HashSet<IQuestion>();
}
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<IQuestion> IQuestions { get; set; }
}
You've got one of the more helpful error messages there - APICategoryController does not have a default constructor, ie: a parameterless constructor.
You either need to use some kind of Dependency Injector for your code to know how to instantiate a concrete class for ICatRepository, or provide a default constructor.
eg:
//a default constructor instantiating a concrete type. Simple, but no good for testing etc.
public APICategoryController()
{
ICatRepository repository = new ConcreteRepository;
this.repository = repository;
}
you need to add default constructor. you should add this to APICategoryController (this is default constructor)
public APICategoryController()
{
}
I am new to ASP.Net MVC and multi-tenancy web application. I have done lots of reading, but being a beginner I just follow what I understand. So I managed to built a sample scenario web application and need to solve the ending part of it. Hope this scenario will be useful for some other beginners as well, but would welcome any other approach. Thanks in advance
1) Database in SQLServer 2008.
2) Data layer: C# class library project called MyApplication.Data
public class AppUser
{
[Key]
public virtual int AppUserID { get; set; }
[Required]
public virtual int TenantID { get; set; }
[Required]
public virtual int EmployeeID { get; set; }
[Required]
public virtual string Login { get; set; }
[Required]
public virtual string Password { get; set; }
}
public class Employee
{
[Key]
public virtual int EmployeeID { get; set; }
[Required]
public virtual int TenantID { get; set; }
[Required]
public virtual string FullName { get; set; }
}
public class Tenant_SYS
{
//this is an autonumber starting from 1
[Key]
public virtual int TenantID { get; set; }
[Required]
public virtual string TenantName { get; set; }
}
3). Business Layer: class library MyApplication.Business
Following FilteredDbSet Class courtesy: Zoran Maksimovic
public class FilteredDbSet<TEntity> : IDbSet<TEntity>, IOrderedQueryable<TEntity>, IOrderedQueryable, IQueryable<TEntity>, IQueryable, IEnumerable<TEntity>, IEnumerable, IListSource
where TEntity : class
{
private readonly DbSet<TEntity> _set;
private readonly Action<TEntity> _initializeEntity;
private readonly Expression<Func<TEntity, bool>> _filter;
public FilteredDbSet(DbContext context)
: this(context.Set<TEntity>(), i => true, null)
{
}
public FilteredDbSet(DbContext context, Expression<Func<TEntity, bool>> filter)
: this(context.Set<TEntity>(), filter, null)
{
}
public FilteredDbSet(DbContext context, Expression<Func<TEntity, bool>> filter, Action<TEntity> initializeEntity)
: this(context.Set<TEntity>(), filter, initializeEntity)
{
}
public Expression<Func<TEntity, bool>> Filter
{
get { return _filter; }
}
public IQueryable<TEntity> Include(string path)
{
return _set.Include(path).Where(_filter).AsQueryable();
}
private FilteredDbSet(DbSet<TEntity> set, Expression<Func<TEntity, bool>> filter, Action<TEntity> initializeEntity)
{
_set = set;
_filter = filter;
MatchesFilter = filter.Compile();
_initializeEntity = initializeEntity;
}
public Func<TEntity, bool> MatchesFilter
{
get;
private set;
}
public IQueryable<TEntity> Unfiltered()
{
return _set;
}
public void ThrowIfEntityDoesNotMatchFilter(TEntity entity)
{
if (!MatchesFilter(entity))
throw new ArgumentOutOfRangeException();
}
public TEntity Add(TEntity entity)
{
DoInitializeEntity(entity);
ThrowIfEntityDoesNotMatchFilter(entity);
return _set.Add(entity);
}
public TEntity Attach(TEntity entity)
{
ThrowIfEntityDoesNotMatchFilter(entity);
return _set.Attach(entity);
}
public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, TEntity
{
var entity = _set.Create<TDerivedEntity>();
DoInitializeEntity(entity);
return (TDerivedEntity)entity;
}
public TEntity Create()
{
var entity = _set.Create();
DoInitializeEntity(entity);
return entity;
}
public TEntity Find(params object[] keyValues)
{
var entity = _set.Find(keyValues);
if (entity == null)
return null;
// If the user queried an item outside the filter, then we throw an error.
// If IDbSet had a Detach method we would use it...sadly, we have to be ok with the item being in the Set.
ThrowIfEntityDoesNotMatchFilter(entity);
return entity;
}
public TEntity Remove(TEntity entity)
{
ThrowIfEntityDoesNotMatchFilter(entity);
return _set.Remove(entity);
}
/// <summary>
/// Returns the items in the local cache
/// </summary>
/// <remarks>
/// It is possible to add/remove entities via this property that do NOT match the filter.
/// Use the <see cref="ThrowIfEntityDoesNotMatchFilter"/> method before adding/removing an item from this collection.
/// </remarks>
public ObservableCollection<TEntity> Local
{
get { return _set.Local; }
}
IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
{
return _set.Where(_filter).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _set.Where(_filter).GetEnumerator();
}
Type IQueryable.ElementType
{
get { return typeof(TEntity); }
}
Expression IQueryable.Expression
{
get
{
return _set.Where(_filter).Expression;
}
}
IQueryProvider IQueryable.Provider
{
get
{
return _set.AsQueryable().Provider;
}
}
bool IListSource.ContainsListCollection
{
get { return false; }
}
IList IListSource.GetList()
{
throw new InvalidOperationException();
}
void DoInitializeEntity(TEntity entity)
{
if (_initializeEntity != null)
_initializeEntity(entity);
}
public DbSqlQuery<TEntity> SqlQuery(string sql, params object[] parameters)
{
return _set.SqlQuery(sql, parameters);
}
}
public class EFDbContext : DbContext
{
public IDbSet<AppUser> AppUser { get; set; }
public IDbSet<Tenant_SYS> Tenant { get; set; }
public IDbSet<Employee> Employee { get; set; }
///this makes sure the naming convention does not have to be plural
///tables can be anything we name them to be
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
public EFDbContext(int tenantID = 0) //Constructor of the class always expect a tenantID
{
//Here, the Dbset can expose the unfiltered data
AppUser = new FilteredDbSet<AppUser>(this);
Tenant = new FilteredDbSet<Tenant_SYS>(this);
//From here, add all the multitenant dbsets with filtered data
Employee = new FilteredDbSet<Employee>(this, d => d.TenantID == tenantID);
}
}
public interface IEmployeeRepository
{
IQueryable<Employee> Employees { get; }
void SaveEmployee(Employee Employee);
void DeleteEmployee(Employee Employee);
List<Employee> GetEmployeesSorted();
}
public class EFEmployeeRepository : IEmployeeRepository
{
private EFDbContext context;
public EFEmployeeRepository(int tenantID = 0)
{
context = new EFDbContext(tenantID);
}
IQueryable<Employee> IEmployeeRepository.Employees
{
get
{
return context.Employee;
}
}
public void SaveEmployee(Employee Employee)
{
if (Employee.EmployeeID == 0)
{
context.Employee.Add(Employee);
}
context.SaveChanges();
}
public void DeleteEmployee(Employee Employee)
{
context.Employee.Remove(Employee);
context.SaveChanges();
}
public List<Employee> GetEmployeesSorted()
{
//This is just a function to see the how the results are fetched.
return context.Employee.OrderBy(m => m.FullName)
.ToList();
//I haven't used where condition to filter the employees since it should be handled by the filtered context
}
}
4) WEB Layer: ASP.NET MVC 4 Internet Application with Ninject DI
public class NinjectControllerFactory : DefaultControllerFactory
{
private IKernel ninjectKernel;
public NinjectControllerFactory()
{
ninjectKernel = new StandardKernel();
AddBindings();
}
protected override IController GetControllerInstance(RequestContext requestContext,
Type controllerType)
{
return controllerType == null
? null
: (IController)ninjectKernel.Get(controllerType);
}
private void AddBindings()
{
ninjectKernel.Bind<IAppUserRepository>().To<EFAppUserRepository>();
ninjectKernel.Bind<IEmployeeRepository>().To<EFEmployeeRepository>();
}
}
5) Controller. Here is the Problem
public class HomeController : Controller
{
IEmployeeRepository repoEmployee;
public HomeController(IEmployeeRepository empRepository)
{
//How can I make sure that the employee is filtered globally by supplying a session variable of tenantID
//Please assume that session variable has been initialized from Login modules after authentication.
//There will be lots of Controllers like this in the application which need to use these globally filtered object
repoEmployee = empRepository;
}
public ActionResult Index()
{
//The list of employees fetched must belong to the tenantID supplied by session variable
//Why this is needed is to secure one tenant's data being exposed to another tenants accidently like, if programmer fails to put where condition
List<Employee> Employees = repoEmployee.Employees.ToList();
return View();
}
}
NInject DI can do the magic !! Provided you will have a login routine which creates the session variable "thisTenantID".
In the Web Layer:
private void AddBindings()
{
//Modified to inject session variable
ninjectKernel.Bind<EFDbContext>().ToMethod(c => new EFDbContext((int)HttpContext.Current.Session["thisTenantID"]));
ninjectKernel.Bind<IAppUserRepository>().To<EFAppUserRepository>();
ninjectKernel.Bind<IEmployeeRepository>().To<EFEmployeeRepository>().WithConstructorArgument("tenantID", c => (int)HttpContext.Current.Session["thisTenantID"]);
}
The way you have designed your repository follows a very clear design, but the parameter that you are passing in the constructor makes things a bit more complicated when using dependency injection.
What I propose here below, is perhaps not the best design, but it will allow you to progress without doing too much changes to your existing code.
The catch in this solution is that you have to call the "Initialise" method when creating the controller, which potentially you might not like, but it is quite effective.
Here are the steps:
Create a new method in your IEmployeeRepository
public interface IEmployeeRepository
{
//leave everything else as it is
void Initialise(int tenantId);
}
Implement that method in the EFEmployeeRepository
public class EFEmployeeRepository
{
//leave everything else as it is
public void Initialise(int tenantID = 0)
{
context = new EFDbContext(tenantID);
}
}
In the HomeController, you would need to call "Initialise" in the constructor
public HomeController(IEmployeeRepository empRepository)
{
repoEmployee = empRepository;
repoEmployee.Initialise(/* use your method to pass the Tenant ID here*/);
}
An alternative to this approach could be to create a RepositoryFactory that would return the Repository filled out with all the filters you need. In that case you will inject the Factory rather than the Repository to the Controller.