I have a solution that contains just one single project (MVC 5 with Identity and EF). The db context is an ApplicationDbContext (a subclass of IdentityDbContext which is itself a subclass of EF DbContext). I also have a custom validation attribute that needs to use the db context to do its thing:
public class RequiredIfRol : ValidationAttribute, IClientValidatable {
//...
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
// ...
string nomRol = "";
//instance and use the ApplicationDbContext
using (ApplicationDbContext db = new ApplicationDbContext()) {
var roldb = db.Roles.Find(rolId);
if (roldb == null) return ValidationResult.Success;
nomRol = db.Roles.Find(rolId).Name;
}
// more code here
}
}
This is working ok.
Now, after reading about it, I am trying to separate this MVC project into several projects:
I am noob on this, since I have always had everything inside the same MVC project, so I am confused: I think the attribute should reside in the Common project, so that it can be referenced from the DAL (to decorate models) and also from the MVC (to decorate viewmodels).
Since the Common project will be referenced from all the other projects, I guess I can not reference any of those projects from the Common (circular reference?). and, as the ApplicationDbContext (which the attribute needs to use) will reside in the DAL project, I have a problem here...
I am pretty sure I am designing this badly, but cant find a correct way to do it.. Any help?
ADDED:
this is something I had tried:
1.- in the common library, defined an interface:
public interface IMyInterface {
System.Data.Entity.IDbSet<CustomRole> Roles { get; set; }
}
2.- in the applicationDbContext, modified its declaration so that it implements the interface:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>, IMyInterface {...}
3.- then in the attribute, try to get an implementation of the interface by adding an getter property:
private IMyInterface _dbcontext { get { return DependencyResolver.Current.GetService<IMyInterface>(); } }
and then in the IsValid() method:
var roldb = _dbcontext.Roles.Find(rolId);
if (roldb == null) return ValidationResult.Success;
nomRol = _dbcontext.Roles.Find(rolId).Name;
but this did not work...
One way around it would be to create an abstraction for your code using context - IRoleFinder
and put in the common project alongside with your RequiredIfRol
. Then implement it in business logic layer and inject it in the MVC project. This way you should be able to decouple you attribute from context.
ValidationAttribute.IsValid()
has a ValidationContext
parameter which you can use to resolve the dependency via ValidationContext.GetService
method.
UPD
Per your request in comment:
For simplification, lets say I have only 2 projects (the class library with the attribute, and the MVC project with everything else).
In library you will have something like this:
interface IRoleFinder
{
CustomRole Find(int id);
}
public class RequiredIfRol : ValidationAttribute, IClientValidatable {
//...
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
// ...
var roleFinder = (IRoleFinder) validationContext.GetService(typeof(IRoleFinder));
}
}
In your MVC project:
public class RoleFinder : IRoleFinder
{
public CustomRole Find(int id)
{
// use context here
}
}
And in Startup (this is for ASP.NET Core, for MVC 5 you should find another way):
services.AddTransient<IRoleFinder, RoleFinder>()