private IModelBinder CreateBinderCoreUncached(DefaultModelBinderProviderContext providerContext, object token) { if (!providerContext.Metadata.IsBindingAllowed) { return(NoOpBinder.Instance); } // A non-null token will usually be passed in at the the top level (ParameterDescriptor likely). // This prevents us from treating a parameter the same as a collection-element - which could // happen looking at just model metadata. var key = new Key(providerContext.Metadata, token); // The providerContext.Visited is used here to break cycles in recursion. We need a separate // per-operation cache for cycle breaking because the global cache (_cache) needs to always stay // in a valid state. // // We store null as a sentinel inside the providerContext.Visited to track the fact that we've visited // a given node but haven't yet created a binder for it. We don't want to eagerly create a // PlaceholderBinder because that would result in lots of unnecessary indirection and allocations. var visited = providerContext.Visited; IModelBinder binder; if (visited.TryGetValue(key, out binder)) { if (binder != null) { return(binder); } // If we're currently recursively building a binder for this type, just return // a PlaceholderBinder. We'll fix it up later to point to the 'real' binder // when the stack unwinds. binder = new PlaceholderBinder(); visited[key] = binder; return(binder); } // OK this isn't a recursive case (yet) so add an entry and then ask the providers // to create the binder. visited.Add(key, null); IModelBinder result = null; for (var i = 0; i < _providers.Length; i++) { var provider = _providers[i]; result = provider.GetBinder(providerContext); if (result != null) { break; } } // If the PlaceholderBinder was created, then it means we recursed. Hook it up to the 'real' binder. var placeholderBinder = visited[key] as PlaceholderBinder; if (placeholderBinder != null) { // It's also possible that user code called into `CreateBinder` but then returned null, we don't // want to create something that will null-ref later so just hook this up to the no-op binder. placeholderBinder.Inner = result ?? NoOpBinder.Instance; } if (result != null) { visited[key] = result; } return(result); }
private IModelBinder CreateBinderCore(DefaultModelBinderProviderContext providerContext, object token) { if (!providerContext.Metadata.IsBindingAllowed) { return(NoOpBinder.Instance); } // A non-null token will usually be passed in at the the top level (ParameterDescriptor likely). // This prevents us from treating a parameter the same as a collection-element - which could // happen looking at just model metadata. var key = new Key(providerContext.Metadata, token); // If we're currently recursively building a binder for this type, just return // a PlaceholderBinder. We'll fix it up later to point to the 'real' binder // when the stack unwinds. var stack = providerContext.Stack; for (var i = 0; i < stack.Count; i++) { var entry = stack[i]; if (key.Equals(entry.Key)) { if (entry.Value == null) { // Recursion detected, create a DelegatingBinder. var binder = new PlaceholderBinder(); stack[i] = new KeyValuePair <Key, PlaceholderBinder>(entry.Key, binder); return(binder); } else { return(entry.Value); } } } // OK this isn't a recursive case (yet) so "push" an entry on the stack and then ask the providers // to create the binder. stack.Add(new KeyValuePair <Key, PlaceholderBinder>(key, null)); IModelBinder result = null; for (var i = 0; i < _providers.Length; i++) { var provider = _providers[i]; result = provider.GetBinder(providerContext); if (result != null) { break; } } if (result == null && stack.Count > 1) { // Use a no-op binder if we're below the top level. At the top level, we throw. result = NoOpBinder.Instance; } // "pop" Debug.Assert(stack.Count > 0); var delegatingBinder = stack[stack.Count - 1].Value; stack.RemoveAt(stack.Count - 1); // If the DelegatingBinder was created, then it means we recursed. Hook it up to the 'real' binder. if (delegatingBinder != null) { delegatingBinder.Inner = result; } return(result); }