Deep Validation with DataAnnotations
The following attribute will allow complex child properties to be validated when using the Validator.TryValidateObject in System.ComponentModel.DataAnnotations.
Writings on .NET, Ruby, JavaScript, HTML/CSS, Design, and all things Web.
The following attribute will allow complex child properties to be validated when using the Validator.TryValidateObject in System.ComponentModel.DataAnnotations.
As I’ve mentioned before my team is working on a large scale ASP.NET MVC application and we’re using dependency injection to decouple our data access layer from the application. Initially we were using NInject as our IoC tool of choice. It’s lightweight and has a really nice configuration DSL and it’s pretty fast.
The problem arose when trying to get NInject’s scope caching to work. It has this nifty feature where it will cache the objects it creates and you could specify the scope of that caching…for example SingletonBehavior, or OnPerSessionBehavior. That’s great but I couldn’t make it work, regardless of the configuration it would always return a brand new object every time.
So then I went and checked out StructureMap. It’s a little more complex to get set up and I personally don’t think it’s registration DSL is as nice as NInject’s but it works. It has a similar caching feature where you can specify an Instance Scope of Application, Session, Request, Thread, etc. Same problem though, no matter how I configured it the caching was always at the Application level. So every session in the app would get back the same objects…not very secure on the data front.
Now, I am completely open to the possibility that I don’t know enough about either of these tools and that the roadblocks I ran into were user error. However, I realized I was spending way too much time on something that is essentially a very simple problem. Here’s what I needed out of an IoC tool:
1. Constructor Injection – I don’t really care about property or method injection, just constructors
2. Cache the Output at Session Level
3. Easy Registration with Constructor Args
So the following is what I came up with to solve the problem:
namespace DI { internal class InjectionBinder { public Type Abstract { get; set; } public Type Concrete { get; set; } public object[] CtorArgs { get; set; } } /// <summary> /// A lightweight dependency injection tool that supports constructor injection and HttpContext.Session caching. /// </summary> public static class Injector { private const string SessionBindingKey = "__InjectorBindings__"; private const string SessionCacheKey = "__InjectorCache__"; private static List<InjectionBinder> _bindings { get { if (HttpContext.Current.Session[SessionBindingKey] == null) HttpContext.Current.Session[SessionBindingKey] = new List<InjectionBinder>(); return (List<InjectionBinder>)HttpContext.Current.Session[SessionBindingKey]; } set { HttpContext.Current.Session[SessionBindingKey] = value; } } private static List<KeyValuePair<Type, object>> _cache { get { if (HttpContext.Current.Session[SessionCacheKey] == null) HttpContext.Current.Session[SessionCacheKey] = new List<KeyValuePair<Type, object>>(); return (List<KeyValuePair<Type, object>>)HttpContext.Current.Session[SessionCacheKey]; } set { HttpContext.Current.Session[SessionCacheKey] = value; } } /// <summary> /// Returns a concrete instance of the specified abstract type using the provided bindings and /// attempts to satisfy that type's constructor arguments. /// </summary> /// <param name="abstractType">The abstract type of the object to initialize.</param> /// <returns></returns> public static object GetInstance(Type abstractType) { // Return the item from the cache if it's in there var cache = _cache.ToDictionary(o => o.Key, o => o.Value); if (cache.ContainsKey(abstractType)) return cache[abstractType]; // If the type is in the bindings, return the concrete var foundBinding = _bindings.Find(b => b.Abstract == abstractType); if (foundBinding != null) return Activator.CreateInstance(foundBinding.Concrete, foundBinding.CtorArgs); object obj = null; var ctors = abstractType.GetConstructors().ToList(); // If the type has no constructor, then just create one if (ctors.Count == 0) return Activator.CreateInstance(abstractType); // If there are constructors, find one which takes arguments of the types we have binders for ctors.ForEach(c => { var prms = c.GetParameters().ToList(); var args = new List<object>(); var haveBindings = true; prms.ForEach(p => { var binding = _bindings.Find(b => b.Abstract == p.ParameterType); if (binding == null) { haveBindings = false; } else { args.Add(Activator.CreateInstance(binding.Concrete, binding.CtorArgs)); } }); if (haveBindings) { obj = Activator.CreateInstance(abstractType, args.ToArray()); } }); return obj; } /// <summary> /// Returns a concrete instance of the specified abstract type using the provided bindings and /// attempts to satisfy that type's constructor arguments. /// </summary> /// <typeparam name="TAbstract">The abstract type of the object to initialize.</typeparam> /// <returns></returns> public static TAbstract GetInstance<TAbstract>() where TAbstract : class { return GetInstance(typeof (TAbstract)) as TAbstract; } /// <summary> /// Creates a binding rule between an abstract type and a concrete type. /// </summary> /// <typeparam name="TAbstract">The abstract type for the binding.</typeparam> /// <typeparam name="TConcrete">The concrete type for the binding</typeparam> /// <param name="ctorArgs">The list of constructor arguments to use when initializing the instance of the concrete type.</param> public static void Bind<TAbstract, TConcrete>(object[] ctorArgs) { var binding = _bindings.Find(b => b.Abstract == typeof(TAbstract)); if (binding != null) { binding.Concrete = typeof(TConcrete); binding.CtorArgs = ctorArgs; } else { _bindings.Add(new InjectionBinder { Abstract = typeof(TAbstract), Concrete = typeof(TConcrete), CtorArgs = ctorArgs }); } } /// <summary> /// Clears all Injector binding rules and empties the internal cache. /// </summary> public static void Kill() { HttpContext.Current.Session[SessionBindingKey] = null; HttpContext.Current.Session[SessionCacheKey] = null; } } }
And here’s how we register the bindings:
private static void BindInjectionContainer() { Injector.Kill(); // Clears out previously created bindings and empties the cache var currentClient = new Client(); // Doesn't matter...just an example var currentUser = new User(); var currentCulture = "en-US"; Injector.Bind<IClientRepository, ClientRepository>(new object[] { currentClient, currentCulture }); Injector.Bind<IUserRepository, UserRepository>(new object[] { currentClient, currentCulture, currentUser }); Injector.Bind<ISecurityRepository, SecurityRepository>(new object[] {}); }
And lastly, here’s how you request an object:
var secRepo = Injector.GetInstance<ISecurityRepository>();
So, it may be ridiculous to roll-your-own on the IoC container but as you can see it was pretty simple to get what I needed. It’s very solution specific but what’s wrong with that. In this instance it’s pretty easy to swap out for something more feature rich if necessary but for now…I call YAGNI on all that.
I love stackoverflow.com! It doesn't get much better than a bunch of geeks answering each others' questions for merit badges
I think we've all encountered the fear of open source software amongst some of our clients. I like to call it ossphobia. It basically amounts to a categorical rejection of anything that doesn't arrive in shrink wrap with a shiny holographic label displaying a Microsoft logo. While I am primarily a Microsoft developer, I am by no means a fanatic and often find that the best solution to a problem is exactly the opposite of the solution championed by Microsoft.
So, now that all that's been said. I'm introducing Echo ORM as an open source project on Google Code. It's a very small framework and in a very alpha state, but I'd love some feedback on the approach that I've taken and I welcome comments or contributions.
An excellent post on project size…as in how many people are on a project.
So, I’m on a plane right now and there are at least five people in my immediate vicinity working on laptops. Every single one of these people is reading email in Outlook. The obvious disgust at Outlook aside, this leads me to the question, “Why don’t we have Wi-Fi on every commercial flight yet?”.
I myself would be incredibly attracted to any airline that offered this service. It wouldn’t even have to be free. I’d happily pay an extra $10 for my ticket if it came with even modestly quick Internet access.
I doubt if any airline execs read this particular blog, but if you are…Wi-Fi…all I have to say…Wi-Fi.