/// <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; }
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); }