/// <exclude/>
 private static string Replace(this string src, string mask, ModelExplorer explorer)
 {
     return Regex.Replace(WebUtility.UrlDecode(src), mask, (m) =>
     {
         var key = m.Groups[1].Value;
         ModelExplorer property = explorer.GetExplorerForProperty(key);
         return (property?.Model?.ToString() ?? m.Groups[0].Value);
     });
 }
        public TemplateBuilder(
            IViewEngine viewEngine,
            IViewBufferScope bufferScope,
            ViewContext viewContext,
            ViewDataDictionary viewData,
            ModelExplorer modelExplorer,
            string htmlFieldName,
            string templateName,
            bool readOnly,
            object additionalViewData)
        {
            if (viewEngine == null)
            {
                throw new ArgumentNullException(nameof(viewEngine));
            }

            if (bufferScope == null)
            {
                throw new ArgumentNullException(nameof(_bufferScope));
            }

            if (viewContext == null)
            {
                throw new ArgumentNullException(nameof(viewContext));
            }

            if (viewData == null)
            {
                throw new ArgumentNullException(nameof(viewData));
            }

            if (modelExplorer == null)
            {
                throw new ArgumentNullException(nameof(modelExplorer));
            }

            _viewEngine = viewEngine;
            _bufferScope = bufferScope;
            _viewContext = viewContext;
            _viewData = viewData;
            _modelExplorer = modelExplorer;
            _htmlFieldName = htmlFieldName;
            _templateName = templateName;
            _readOnly = readOnly;
            _additionalViewData = additionalViewData;

            _model = modelExplorer.Model;
            _metadata = modelExplorer.Metadata;
        }
        public TemplateBuilder([NotNull] IViewEngine viewEngine,
                               [NotNull] ViewContext viewContext,
                               [NotNull] ViewDataDictionary viewData,
                               [NotNull] ModelExplorer modelExplorer,
                               string htmlFieldName,
                               string templateName,
                               bool readOnly,
                               object additionalViewData)
        {
            _viewEngine = viewEngine;
            _viewContext = viewContext;
            _viewData = viewData;
            _modelExplorer = modelExplorer;
            _htmlFieldName = htmlFieldName;
            _templateName = templateName;
            _readOnly = readOnly;
            _additionalViewData = additionalViewData;

            _model = modelExplorer.Model;
            _metadata = modelExplorer.Metadata;
        }
        private TagBuilder GenerateRadio(ModelExplorer modelExplorer)
        {
            // Note empty string is allowed.
            if (Value == null)
            {
                throw new InvalidOperationException(Resources.FormatInputTagHelper_ValueRequired(
                    "<input>",
                    nameof(Value).ToLowerInvariant(),
                    "type",
                    "radio"));
            }

            return Generator.GenerateRadioButton(
                ViewContext,
                modelExplorer,
                For.Name,
                Value,
                isChecked: null,
                htmlAttributes: null);
        }
        private void GenerateCheckBox(ModelExplorer modelExplorer, TagHelperOutput output)
        {
            if (typeof(bool) != modelExplorer.ModelType)
            {
                throw new InvalidOperationException(Resources.FormatInputTagHelper_InvalidExpressionResult(
                    "<input>",
                    ForAttributeName,
                    modelExplorer.ModelType.FullName,
                    typeof(bool).FullName,
                    "type",
                    "checkbox"));
            }

            // Prepare to move attributes from current element to <input type="checkbox"/> generated just below.
            var htmlAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

            // Construct attributes correctly (first attribute wins).
            foreach (var attribute in output.Attributes)
            {
                if (!htmlAttributes.ContainsKey(attribute.Name))
                {
                    htmlAttributes.Add(attribute.Name, attribute.Value);
                }
            }

            var tagBuilder = Generator.GenerateCheckBox(
                ViewContext,
                modelExplorer,
                For.Name,
                isChecked: null,
                htmlAttributes: htmlAttributes);
            if (tagBuilder != null)
            {
                // Do not generate current element's attributes or tags. Instead put both <input type="checkbox"/> and
                // <input type="hidden"/> into the output's Content.
                output.Attributes.Clear();
                output.TagName = null;

                output.Content.Append(tagBuilder.ToString(TagRenderMode.SelfClosing));

                tagBuilder = Generator.GenerateHiddenForCheckbox(ViewContext, modelExplorer, For.Name);
                if (tagBuilder != null)
                {
                    output.Content.Append(tagBuilder.ToString(TagRenderMode.SelfClosing));
                }
            }
        }
 protected override IDictionary<string, object> GetValidationAttributes(
     ViewContext viewContext,
     ModelExplorer modelExplorer,
     string name)
 {
     return ValidationAttributes;
 }
 /// <exclude/>
 public static string ReplaceStringTokens(this object src, ModelExplorer explorer)
     => ReplaceStringTokens(src.ToString(), explorer);
        public static string CollectionTemplate(IHtmlHelper htmlHelper)
        {
            var model = htmlHelper.ViewData.Model;
            if (model == null)
            {
                return string.Empty;
            }

            var collection = model as IEnumerable;
            if (collection == null)
            {
                // Only way we could reach here is if user passed templateName: "Collection" to a Display() overload.
                throw new InvalidOperationException(Resources.FormatTemplates_TypeMustImplementIEnumerable(
                    "Collection", model.GetType().FullName, typeof(IEnumerable).FullName));
            }

            var elementMetadata = htmlHelper.ViewData.ModelMetadata.ElementMetadata;
            Debug.Assert(elementMetadata != null);
            var typeInCollectionIsNullableValueType = elementMetadata.IsNullableValueType;

            var serviceProvider = htmlHelper.ViewContext.HttpContext.RequestServices;
            var metadataProvider = serviceProvider.GetRequiredService<IModelMetadataProvider>();

            // Use typeof(string) instead of typeof(object) for IEnumerable collections. Neither type is Nullable<T>.
            if (elementMetadata.ModelType == typeof(object))
            {
                elementMetadata = metadataProvider.GetMetadataForType(typeof(string));
            }

            var oldPrefix = htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix;
            try
            {
                htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix = string.Empty;

                var fieldNameBase = oldPrefix;
                var result = new StringBuilder();
                var viewEngine = serviceProvider.GetRequiredService<ICompositeViewEngine>();

                var index = 0;
                foreach (var item in collection)
                {
                    var itemMetadata = elementMetadata;
                    if (item != null && !typeInCollectionIsNullableValueType)
                    {
                        itemMetadata = metadataProvider.GetMetadataForType(item.GetType());
                    }

                    var modelExplorer = new ModelExplorer(
                        metadataProvider,
                        container: htmlHelper.ViewData.ModelExplorer,
                        metadata: itemMetadata,
                        model: item);
                    var fieldName = string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", fieldNameBase, index++);

                    var templateBuilder = new TemplateBuilder(
                        viewEngine,
                        htmlHelper.ViewContext,
                        htmlHelper.ViewData,
                        modelExplorer,
                        htmlFieldName: fieldName,
                        templateName: null,
                        readOnly: true,
                        additionalViewData: null);

                    var output = templateBuilder.Build();
                    result.Append(output);
                }

                return result.ToString();
            }
            finally
            {
                htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix = oldPrefix;
            }
        }
 public new void SetProperty(
     ModelBindingContext bindingContext,
     ModelExplorer modelExplorer,
     ModelMetadata propertyMetadata,
     ModelBindingResult result)
 {
     base.SetProperty(bindingContext, modelExplorer, propertyMetadata, result);
 }
        private string GetInputType(ModelExplorer modelExplorer, out string inputTypeHint)
        {
            foreach (var hint in GetInputTypeHints(modelExplorer))
            {
                string inputType;
                if (_defaultInputTypes.TryGetValue(hint, out inputType))
                {
                    inputTypeHint = hint;
                    return inputType;
                }
            }

            inputTypeHint = InputType.Text.ToString().ToLowerInvariant();
            return inputTypeHint;
        }
Exemple #11
0
 public bool Visited(ModelExplorer modelExplorer)
 {
     return _visitedObjects.Contains(modelExplorer.Model ?? modelExplorer.Metadata.ModelType);
 }
        // Returns true if validator execution adds a model error.
        private static bool RunValidator(
            IModelValidator validator,
            ModelBindingContext bindingContext,
            ModelExplorer propertyExplorer,
            string modelStateKey)
        {
            var validationContext = new ModelValidationContext(bindingContext, propertyExplorer);

            var addedError = false;
            foreach (var validationResult in validator.Validate(validationContext))
            {
                bindingContext.ModelState.TryAddModelError(modelStateKey, validationResult.Message);
                addedError = true;
            }

            if (!addedError)
            {
                bindingContext.ModelState.MarkFieldValid(modelStateKey);
            }

            return addedError;
        }
        protected virtual void SetProperty(
            ModelBindingContext bindingContext,
            ModelExplorer modelExplorer,
            ModelMetadata propertyMetadata,
            ModelBindingResult dtoResult,
            IModelValidator requiredValidator)
        {
            var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase;
            var property = bindingContext.ModelType.GetProperty(
                propertyMetadata.PropertyName,
                bindingFlags);

            if (property == null || !property.CanWrite)
            {
                // nothing to do
                return;
            }

            object value;
            var hasDefaultValue = false;
            if (dtoResult.IsModelSet)
            {
                value = dtoResult.Model;
            }
            else
            {
                hasDefaultValue = TryGetPropertyDefaultValue(property, out value);
            }

            // 'Required' validators need to run first so that we can provide useful error messages if
            // the property setters throw, e.g. if we're setting entity keys to null.
            if (value == null)
            {
                var modelStateKey = dtoResult.Key;
                var validationState = bindingContext.ModelState.GetFieldValidationState(modelStateKey);
                if (validationState == ModelValidationState.Unvalidated)
                {
                    if (requiredValidator != null)
                    {
                        var propertyExplorer = modelExplorer.GetExplorerForExpression(propertyMetadata, model: null);
                        var validationContext = new ModelValidationContext(bindingContext, propertyExplorer);
                        foreach (var validationResult in requiredValidator.Validate(validationContext))
                        {
                            bindingContext.ModelState.TryAddModelError(modelStateKey, validationResult.Message);
                        }
                    }
                }
            }

            if (!dtoResult.IsModelSet && !hasDefaultValue)
            {
                // If we don't have a value, don't set it on the model and trounce a pre-initialized
                // value.
                return;
            }

            if (value != null || property.PropertyType.AllowsNullValue())
            {
                try
                {
                    property.SetValue(bindingContext.Model, value);
                }
                catch (Exception ex)
                {
                    // don't display a duplicate error message if a binding error has already occurred for this field
                    var targetInvocationException = ex as TargetInvocationException;
                    if (targetInvocationException != null &&
                        targetInvocationException.InnerException != null)
                    {
                        ex = targetInvocationException.InnerException;
                    }
                    var modelStateKey = dtoResult.Key;
                    var validationState = bindingContext.ModelState.GetFieldValidationState(modelStateKey);
                    if (validationState == ModelValidationState.Unvalidated)
                    {
                        bindingContext.ModelState.AddModelError(modelStateKey, ex);
                    }
                }
            }
            else
            {
                // trying to set a non-nullable value type to null, need to make sure there's a message
                var modelStateKey = dtoResult.Key;
                var validationState = bindingContext.ModelState.GetFieldValidationState(modelStateKey);
                if (validationState == ModelValidationState.Unvalidated)
                {
                    var errorMessage = Resources.ModelBinderConfig_ValueRequired;
                    bindingContext.ModelState.TryAddModelError(modelStateKey, errorMessage);
                }
            }
        }
 /// <exclude/>
 public static string ReplaceStringTokens(this string src, ModelExplorer explorer)
 {
     return src.Replace(@"{{(\w+)}}", explorer).Replace(@"{(\w+)}", explorer);
 }
        private TagBuilder GenerateTextBox(ModelExplorer modelExplorer, string inputTypeHint, string inputType)
        {
            var format = Format;
            if (string.IsNullOrEmpty(format))
            {
                format = GetFormat(modelExplorer, inputTypeHint, inputType);
            }

            var htmlAttributes = new Dictionary<string, object>
            {
                { "type", inputType }
            };

            if (string.Equals(inputType, "file") && string.Equals(inputTypeHint, TemplateRenderer.IEnumerableOfIFormFileName))
            {
                htmlAttributes["multiple"] = "multiple";
            }

            return Generator.GenerateTextBox(
                ViewContext,
                modelExplorer,
                For.Name,
                value: modelExplorer.Model,
                format: format,
                htmlAttributes: htmlAttributes);
        }
        // Get a fall-back format based on the metadata.
        private string GetFormat(ModelExplorer modelExplorer, string inputTypeHint, string inputType)
        {
            string format;
            string rfc3339Format;
            if (string.Equals("decimal", inputTypeHint, StringComparison.OrdinalIgnoreCase) &&
                string.Equals("text", inputType, StringComparison.Ordinal) &&
                string.IsNullOrEmpty(modelExplorer.Metadata.EditFormatString))
            {
                // Decimal data is edited using an <input type="text"/> element, with a reasonable format.
                // EditFormatString has precedence over this fall-back format.
                format = "{0:0.00}";
            }
            else if (_rfc3339Formats.TryGetValue(inputType, out rfc3339Format) &&
                ViewContext.Html5DateRenderingMode == Html5DateRenderingMode.Rfc3339 &&
                !modelExplorer.Metadata.HasNonDefaultEditFormat &&
                (typeof(DateTime) == modelExplorer.ModelType || typeof(DateTimeOffset) == modelExplorer.ModelType))
            {
                // Rfc3339 mode _may_ override EditFormatString in a limited number of cases e.g. EditFormatString
                // must be a default format (i.e. came from a built-in [DataType] attribute).
                format = rfc3339Format;
            }
            else
            {
                // Otherwise use EditFormatString, if any.
                format = modelExplorer.Metadata.EditFormatString;
            }

            return format;
        }
        private async Task PopulateArgumentsAsync(
            OperationBindingContext operationContext,
            ModelStateDictionary modelState,
            IDictionary<string, object> arguments,
            IEnumerable<ParameterDescriptor> parameterMetadata)
        {
            modelState.MaxAllowedErrors = _options.MaxModelValidationErrors;
            foreach (var parameter in parameterMetadata)
            {
                var metadata = _modelMetadataProvider.GetMetadataForType(parameter.ParameterType);
                var parameterType = parameter.ParameterType;
                var modelBindingContext = GetModelBindingContext(
                    parameter.Name,
                    metadata,
                    parameter.BindingInfo,
                    modelState,
                    operationContext);

                var modelBindingResult = await operationContext.ModelBinder.BindModelAsync(modelBindingContext);
                if (modelBindingResult != null && modelBindingResult.IsModelSet)
                {
                    var modelExplorer = new ModelExplorer(
                        _modelMetadataProvider,
                        metadata,
                        modelBindingResult.Model);

                    arguments[parameter.Name] = modelBindingResult.Model;
                    var validationContext = new ModelValidationContext(
                        modelBindingResult.Key,
                        modelBindingContext.BindingSource,
                        operationContext.ValidatorProvider,
                        modelState,
                        modelExplorer);
                    _validator.Validate(validationContext);
                }
            }
        }
        // A variant of TemplateRenderer.GetViewNames(). Main change relates to bool? handling.
        private static IEnumerable<string> GetInputTypeHints(ModelExplorer modelExplorer)
        {
            var inputTypeHints = new string[]
            {
                modelExplorer.Metadata.TemplateHint,
                modelExplorer.Metadata.DataTypeName,
            };

            foreach (string inputTypeHint in inputTypeHints.Where(s => !string.IsNullOrEmpty(s)))
            {
                yield return inputTypeHint;
            }

            // In most cases, we don't want to search for Nullable<T>. We want to search for T, which should handle
            // both T and Nullable<T>. However we special-case bool? to avoid turning an <input/> into a <select/>.
            var fieldType = modelExplorer.ModelType;
            if (typeof(bool?) != fieldType)
            {
                var underlyingType = Nullable.GetUnderlyingType(fieldType);
                if (underlyingType != null)
                {
                    fieldType = underlyingType;
                }
            }

            foreach (string typeName in TemplateRenderer.GetTypeNames(modelExplorer.Metadata, fieldType))
            {
                yield return typeName;
            }
        }
        private void GenerateCheckBox(ModelExplorer modelExplorer, TagHelperOutput output)
        {
            if (typeof(bool) != modelExplorer.ModelType)
            {
                throw new InvalidOperationException(Resources.FormatInputTagHelper_InvalidExpressionResult(
                    "<input>",
                    ForAttributeName,
                    modelExplorer.ModelType.FullName,
                    typeof(bool).FullName,
                    "type",
                    "checkbox"));
            }

            // Prepare to move attributes from current element to <input type="checkbox"/> generated just below.
            var htmlAttributes = output.Attributes.ToDictionary(
                attribute => attribute.Key,
                attribute => (object)attribute.Value);

            var tagBuilder = Generator.GenerateCheckBox(
                ViewContext,
                modelExplorer,
                For.Name,
                isChecked: null,
                htmlAttributes: htmlAttributes);
            if (tagBuilder != null)
            {
                // Do not generate current element's attributes or tags. Instead put both <input type="checkbox"/> and
                // <input type="hidden"/> into the output's Content.
                output.Attributes.Clear();
                output.TagName = null;

                output.Content.Append(tagBuilder.ToString(TagRenderMode.SelfClosing));

                tagBuilder = Generator.GenerateHiddenForCheckbox(ViewContext, modelExplorer, For.Name);
                if (tagBuilder != null)
                {
                    output.Content.Append(tagBuilder.ToString(TagRenderMode.SelfClosing));
                }
            }
        }
 private static bool ShouldShow(ModelExplorer modelExplorer, TemplateInfo templateInfo)
 {
     return
         modelExplorer.Metadata.ShowForDisplay &&
         !modelExplorer.Metadata.IsComplexType &&
         !templateInfo.Visited(modelExplorer);
 }
        private TagBuilder GenerateTextBox(ModelExplorer modelExplorer, string inputTypeHint, string inputType)
        {
            var format = Format;
            if (string.IsNullOrEmpty(format))
            {
                format = GetFormat(modelExplorer, inputTypeHint, inputType);
            }

            return Generator.GenerateTextBox(
                ViewContext,
                modelExplorer,
                For.Name,
                value: modelExplorer.Model,
                format: Format,
                htmlAttributes: null);
        }
        // A variant of TemplateRenderer.GetViewNames(). Main change relates to bool? handling.
        private static IEnumerable<string> GetInputTypeHints(ModelExplorer modelExplorer)
        {
            var inputTypeHints = new string[]
            {
                modelExplorer.Metadata.TemplateHint,
                modelExplorer.Metadata.DataTypeName,
            };

            foreach (string inputTypeHint in inputTypeHints.Where(s => !string.IsNullOrEmpty(s)))
            {
                yield return inputTypeHint;
            }

            // In most cases, we don't want to search for Nullable<T>. We want to search for T, which should handle
            // both T and Nullable<T>. However we special-case bool? to avoid turning an <input/> into a <select/>.
            var fieldType = modelExplorer.ModelType;
            if (typeof(bool?) != fieldType)
            {
                var underlyingType = Nullable.GetUnderlyingType(fieldType);
                if (underlyingType != null)
                {
                    fieldType = underlyingType;
                }
            }

            yield return fieldType.Name;

            if (fieldType == typeof(string))
            {
                // Nothing more to provide
                yield break;
            }
            else if (!modelExplorer.Metadata.IsComplexType)
            {
                // IsEnum is false for the Enum class itself
                if (fieldType.IsEnum())
                {
                    // Same as fieldType.BaseType.Name in this case
                    yield return "Enum";
                }
                else if (fieldType == typeof(DateTimeOffset))
                {
                    yield return "DateTime";
                }

                yield return "String";
            }
            else if (fieldType.IsInterface())
            {
                if (typeof(IEnumerable).IsAssignableFrom(fieldType))
                {
                    yield return "Collection";
                }

                yield return "Object";
            }
            else
            {
                var isEnumerable = typeof(IEnumerable).IsAssignableFrom(fieldType);
                while (true)
                {
                    fieldType = fieldType.BaseType();
                    if (fieldType == null)
                    {
                        break;
                    }

                    if (isEnumerable && fieldType == typeof(Object))
                    {
                        yield return "Collection";
                    }

                    yield return fieldType.Name;
                }
            }
        }
 public new void SetProperty(
     ModelBindingContext bindingContext,
     ModelExplorer modelExplorer,
     ModelMetadata propertyMetadata,
     ModelBindingResult dtoResult,
     IModelValidator requiredValidator)
 {
     base.SetProperty(
         bindingContext,
         modelExplorer,
         propertyMetadata,
         dtoResult,
         requiredValidator);
 }