/// <summary> /// Creates a new <see cref="ModelExplorer"/>. /// </summary> /// <param name="metadataProvider">The <see cref="IModelMetadataProvider"/>.</param> /// <param name="container">The container <see cref="ModelExplorer"/>.</param> /// <param name="metadata">The <see cref="ModelMetadata"/>.</param> /// <param name="modelAccessor">A model accessor function. May be <c>null</c>.</param> public ModelExplorer( IModelMetadataProvider metadataProvider, ModelExplorer container, ModelMetadata metadata, Func<object, object> modelAccessor) { if (metadataProvider == null) { throw new ArgumentNullException(nameof(metadataProvider)); } if (container == null) { throw new ArgumentNullException(nameof(container)); } if (metadata == null) { throw new ArgumentNullException(nameof(metadata)); } _metadataProvider = metadataProvider; Container = container; Metadata = metadata; _modelAccessor = modelAccessor; }
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; }
/// <summary> /// Initializes a new instance of the <see cref="ModelExpression"/> class. /// </summary> /// <param name="name"> /// String representation of the <see cref="System.Linq.Expressions.Expression"/> of interest. /// </param> /// <param name="modelExplorer"> /// Includes the model and metadata about the <see cref="System.Linq.Expressions.Expression"/> of interest. /// </param> public ModelExpression(string name, ModelExplorer modelExplorer) { if (name == null) { throw new ArgumentNullException(nameof(name)); } if (modelExplorer == null) { throw new ArgumentNullException(nameof(modelExplorer)); } Name = name; ModelExplorer = modelExplorer; }
public static string GetModelErrorMessageOrDefault( ModelError modelError, ModelStateEntry containingEntry, ModelExplorer modelExplorer) { Debug.Assert(modelError != null); Debug.Assert(containingEntry != null); Debug.Assert(modelExplorer != null); if (!string.IsNullOrEmpty(modelError.ErrorMessage)) { return modelError.ErrorMessage; } // Default in the ValidationMessage case is a fallback error message. var attemptedValue = containingEntry.AttemptedValue ?? "null"; return modelExplorer.Metadata.ModelBindingMessageProvider.ValueIsInvalidAccessor(attemptedValue); }
// Only render attributes if client-side validation is enabled, and then only if we've // never rendered validation for a field with this name in this form. protected override IDictionary<string, object> GetValidationAttributes(ViewContext viewContext, ModelExplorer modelExplorer, string expression) { var formContext = viewContext.ClientValidationEnabled ? viewContext.FormContext : null; if (formContext == null) { return null; } var fullName = GetFullHtmlFieldName(viewContext, expression); if (formContext.RenderedField(fullName)) { return null; } formContext.RenderedField(fullName, true); var clientRules = GetClientValidationRules(viewContext, modelExplorer, expression); // use inbuilt Unobtrusive validation when the parsley attribute is not used if (viewContext.ViewData.ContainsKey(ParsleyValidationAttributesGenerator.USE_PARSLEY_KEY) && (bool)viewContext.ViewData[ParsleyValidationAttributesGenerator.USE_PARSLEY_KEY] == true) return ParsleyValidationAttributesGenerator.GetValidationAttributes(clientRules); else return UnobtrusiveValidationAttributesGenerator.GetValidationAttributes(clientRules); }
public void ValidateSetsMemberNamePropertyOfValidationContextForProperties( ModelExplorer modelExplorer, string expectedMemberName) { // Arrange var attribute = new Mock<ValidationAttribute> { CallBase = true }; attribute.Protected() .Setup<ValidationResult>("IsValid", ItExpr.IsAny<object>(), ItExpr.IsAny<ValidationContext>()) .Callback((object o, ValidationContext context) => { Assert.Equal(expectedMemberName, context.MemberName); }) .Returns(ValidationResult.Success) .Verifiable(); var validator = new DataAnnotationsModelValidator(attribute.Object); var validationContext = CreateValidationContext(modelExplorer); // Act var results = validator.Validate(validationContext); // Assert Assert.Empty(results); attribute.VerifyAll(); }
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); }
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); // Perf: Avoid allocating enumerator // Construct attributes correctly (first attribute wins). for (var i = 0; i < output.Attributes.Count; i++) { var attribute = output.Attributes[i]; if (!htmlAttributes.ContainsKey(attribute.Name)) { htmlAttributes.Add(attribute.Name, attribute.Value); } } var checkBoxTag = Generator.GenerateCheckBox( ViewContext, modelExplorer, For.Name, isChecked: null, htmlAttributes: htmlAttributes); if (checkBoxTag != 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; var renderingMode = output.TagMode == TagMode.SelfClosing ? TagRenderMode.SelfClosing : TagRenderMode.StartTag; checkBoxTag.TagRenderMode = renderingMode; output.Content.AppendHtml(checkBoxTag); var hiddenForCheckboxTag = Generator.GenerateHiddenForCheckbox(ViewContext, modelExplorer, For.Name); if (hiddenForCheckboxTag != null) { hiddenForCheckboxTag.TagRenderMode = renderingMode; if (ViewContext.FormContext.CanRenderAtEndOfForm) { ViewContext.FormContext.EndOfFormContent.Add(hiddenForCheckboxTag); } else { output.Content.AppendHtml(hiddenForCheckboxTag); } } } }
public static IEnumerable <IDictionary <string, object> > GetValidationRules(string propertyName, ModelExplorer modelExplorer, ViewContext viewContext) { List <IDictionary <string, object> > list = new List <IDictionary <string, object> >(); ModelMetadata modelMetadata = modelExplorer?.Metadata; if (modelMetadata != null) { IDictionary <string, string> validationAttributes = GetValidationAttributes(viewContext, modelExplorer, propertyName); { foreach (KeyValuePair <string, string> item in validationAttributes) { string text = item.Key.Replace("data-val-", ""); bool flag = text.StartsWith("custom") && text.Split(new char[1] { '-' }).Length == 2; if ((validationTypesMap.ContainsKey(text) && !SkipValidationRule(text, modelMetadata)) | flag) { Dictionary <string, object> dictionary = new Dictionary <string, object>(); string text2 = "data-val-" + text + "-"; foreach (KeyValuePair <string, string> item2 in validationAttributes) { if (item2.Key.Contains(text2)) { string text3 = item2.Key.Replace(text2, ""); object value = (text3 != "pattern") ? ((object)new JS(item2.Value)) : item2.Value; dictionary.Add(text3, value); } } text = (flag ? "custom" : text); list.Add(CreateClientValidationRule(propertyName, text, item.Value, dictionary)); } } return(list); } } return(list); }
protected virtual bool IsRadioGroup(ModelExplorer explorer) { return(explorer.GetAttribute <AbpRadioButton>() != null); }
private static ModelValidationContext CreateValidationContext(ModelExplorer modelExplorer) { return new ModelValidationContext( bindingSource: null, modelState: null, validatorProvider: null, modelExplorer: modelExplorer); }
private bool ValidateElements(string currentKey, IEnumerable model, ValidationContext validationContext) { var elementType = GetElementType(model.GetType()); var elementMetadata = _modelMetadataProvider.GetMetadataForType(elementType); var validatorProvider = validationContext.ModelValidationContext.ValidatorProvider; var validatorProviderContext = new ModelValidatorProviderContext(elementMetadata); validatorProvider.GetValidators(validatorProviderContext); var validators = validatorProviderContext.Validators; // If there are no validators or the object is null we bail out quickly // when there are large arrays of null, this will save a significant amount of processing // with minimal impact to other scenarios. var anyValidatorsDefined = validators.Any(); var index = 0; var isValid = true; foreach (var element in model) { // If the element is non null, the recursive calls might find more validators. // If it's null, then a shallow validation will be performed. if (element != null || anyValidatorsDefined) { var elementExplorer = new ModelExplorer(_modelMetadataProvider, elementMetadata, element); var elementKey = ModelBindingHelper.CreateIndexModelName(currentKey, index); var elementValidationContext = new ValidationContext() { ModelValidationContext = ModelValidationContext.GetChildValidationContext( validationContext.ModelValidationContext, elementExplorer), Visited = validationContext.Visited }; if (!ValidateNonVisitedNodeAndChildren(elementKey, elementValidationContext, validators)) { isValid = false; } } index++; } return isValid; }
private static bool ShouldShow(ModelExplorer modelExplorer, TemplateInfo templateInfo) { return modelExplorer.Metadata.ShowForEdit && !modelExplorer.Metadata.IsComplexType && !templateInfo.Visited(modelExplorer); }
protected virtual List <ModelExpression> ExploreModelsRecursively(List <ModelExpression> list, ModelExplorer model) { if (model.GetAttribute <DynamicFormIgnore>() != null) { return(list); } if (IsCsharpClassOrPrimitive(model.ModelType) || IsListOfCsharpClassOrPrimitive(model.ModelType)) { list.Add(ModelExplorerToModelExpressionConverter(model)); return(list); } if (IsListOfSelectItem(model.ModelType)) { return(list); } return(model.Properties.Aggregate(list, ExploreModelsRecursively)); }
// A variant of TemplateRenderer.GetViewNames(). Main change relates to bool? handling. private static IEnumerable<string> GetInputTypeHints(ModelExplorer modelExplorer) { if (!string.IsNullOrEmpty(modelExplorer.Metadata.TemplateHint)) { yield return modelExplorer.Metadata.TemplateHint; } if (!string.IsNullOrEmpty(modelExplorer.Metadata.DataTypeName)) { yield return modelExplorer.Metadata.DataTypeName; } // 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) { fieldType = modelExplorer.Metadata.UnderlyingOrModelType; } foreach (string typeName in TemplateRenderer.GetTypeNames(modelExplorer.Metadata, fieldType)) { yield return typeName; } }
public IList<ModelExplorer> GetPropertiesForList(ModelExplorer explorer) { var metadata = explorer.Properties.Where(x => ((DefaultModelMetadata)x.Metadata).Attributes.PropertyAttributes.OfType<ShowListAttribute>().Any()); return metadata.ToList(); }
/// <summary> /// Creates a new <see cref="ModelExplorer"/>. /// </summary> /// <param name="metadataProvider">The <see cref="IModelMetadataProvider"/>.</param> /// <param name="container">The container <see cref="ModelExplorer"/>.</param> /// <param name="metadata">The <see cref="ModelMetadata"/>.</param> /// <param name="model">The model object. May be <c>null</c>.</param> public ModelExplorer( IModelMetadataProvider metadataProvider, ModelExplorer container, ModelMetadata metadata, object model) { if (metadataProvider == null) { throw new ArgumentNullException(nameof(metadataProvider)); } if (metadata == null) { throw new ArgumentNullException(nameof(metadata)); } _metadataProvider = metadataProvider; Container = container; Metadata = metadata; _model = model; }
protected virtual bool IsEnum(ModelExplorer explorer) { return(explorer.Metadata.IsEnum); }
// 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 static IDictionary <string, string> GetValidationAttributes(ViewContext viewContext, ModelExplorer modelExplorer, string propertyName) { Dictionary <string, string> dictionary = new Dictionary <string, string>(); viewContext.HttpContext.RequestServices.GetService <ValidationHtmlAttributeProvider>()?.AddValidationAttributes(viewContext, modelExplorer, dictionary); return(dictionary); }
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; }
protected virtual bool AreSelectItemsProvided(ModelExplorer explorer) { return(explorer.GetAttribute <SelectItems>() != null); }
private bool ValidateProperties( string currentModelKey, ModelExplorer modelExplorer, ValidationContext validationContext) { var isValid = true; foreach (var property in modelExplorer.Metadata.Properties) { var propertyExplorer = modelExplorer.GetExplorerForProperty(property.PropertyName); var propertyMetadata = propertyExplorer.Metadata; var propertyValidationContext = new ValidationContext() { ModelValidationContext = ModelValidationContext.GetChildValidationContext( validationContext.ModelValidationContext, propertyExplorer), Visited = validationContext.Visited }; var propertyBindingName = propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName; var childKey = ModelBindingHelper.CreatePropertyModelName(currentModelKey, propertyBindingName); if (!ValidateNonVisitedNodeAndChildren( childKey, propertyValidationContext, validators: null)) { isValid = false; } } return isValid; }
protected override IDictionary<string, object> GetValidationAttributes( ViewContext viewContext, ModelExplorer modelExplorer, string name) { return ValidationAttributes; }
// Validates a single node (not including children) // Returns true if validation passes successfully private static bool ShallowValidate( string modelKey, ModelExplorer modelExplorer, ValidationContext validationContext, IList<IModelValidator> validators) { var isValid = true; var modelState = validationContext.ModelValidationContext.ModelState; var fieldValidationState = modelState.GetFieldValidationState(modelKey); if (fieldValidationState == ModelValidationState.Invalid) { // Even if we have no validators it's possible that model binding may have added a // validation error (conversion error, missing data). We want to still run // validators even if that's the case. isValid = false; } // When the are no validators we bail quickly. This saves a GetEnumerator allocation. // In a large array (tens of thousands or more) scenario it's very significant. if (validators == null || validators.Count > 0) { var modelValidationContext = ModelValidationContext.GetChildValidationContext( validationContext.ModelValidationContext, modelExplorer); var modelValidationState = modelState.GetValidationState(modelKey); // If either the model or its properties are unvalidated, validate them now. if (modelValidationState == ModelValidationState.Unvalidated || fieldValidationState == ModelValidationState.Unvalidated) { foreach (var validator in validators) { foreach (var error in validator.Validate(modelValidationContext)) { var errorKey = ModelBindingHelper.CreatePropertyModelName(modelKey, error.MemberName); if (!modelState.TryAddModelError(errorKey, error.Message) && modelState.GetFieldValidationState(errorKey) == ModelValidationState.Unvalidated) { // If we are not able to add a model error // for instance when the max error count is reached, mark the model as skipped. modelState.MarkFieldSkipped(errorKey); } isValid = false; } } } } if (isValid) { validationContext.ModelValidationContext.ModelState.MarkFieldValid(modelKey); } return isValid; }
public void ModelSetter_SetNullableNonNull_UpdatesModelExplorer() { // Arrange var metadataProvider = new EmptyModelMetadataProvider(); var metadata = metadataProvider.GetMetadataForType(typeof(bool?)); var explorer = new ModelExplorer(metadataProvider, metadata, model: null); var viewData = new ViewDataDictionary(metadataProvider) { ModelExplorer = explorer, }; // Act viewData.Model = true; // Assert Assert.NotNull(viewData.ModelMetadata); Assert.NotNull(viewData.ModelExplorer); Assert.Same(metadata, viewData.ModelMetadata); Assert.Same(metadata.ModelType, explorer.ModelType); Assert.NotSame(explorer, viewData.ModelExplorer); Assert.Equal(viewData.Model, viewData.ModelExplorer.Model); var model = Assert.IsType<bool>(viewData.Model); Assert.True(model); }
public static IHtmlContent CollectionTemplate(IHtmlHelper htmlHelper) { var viewData = htmlHelper.ViewData; var model = viewData.Model; if (model == null) { return HtmlString.Empty; } var collection = model as IEnumerable; if (collection == null) { // Only way we could reach here is if user passed templateName: "Collection" to an Editor() 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 = viewData.TemplateInfo.HtmlFieldPrefix; try { viewData.TemplateInfo.HtmlFieldPrefix = string.Empty; var fieldNameBase = oldPrefix; var result = new HtmlContentBuilder(); var viewEngine = serviceProvider.GetRequiredService<ICompositeViewEngine>(); var viewBufferScope = serviceProvider.GetRequiredService<IViewBufferScope>(); 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, viewBufferScope, htmlHelper.ViewContext, htmlHelper.ViewData, modelExplorer, htmlFieldName: fieldName, templateName: null, readOnly: false, additionalViewData: null); result.AppendHtml(templateBuilder.Build()); } return result; } finally { viewData.TemplateInfo.HtmlFieldPrefix = oldPrefix; } }
protected ViewDataDictionary(ViewDataDictionary source, object?model, Type declaredModelType) : this(source._metadataProvider, source.ModelState, declaredModelType, data : new CopyOnWriteDictionary <string, object?>(source, StringComparer.OrdinalIgnoreCase), templateInfo : new TemplateInfo(source.TemplateInfo)) { if (source == null) { throw new ArgumentNullException(nameof(source)); } // A non-null Model must always be assignable to both _declaredModelType and ModelMetadata.ModelType. // // ModelMetadata.ModelType should also be assignable to _declaredModelType. Though corner cases exist such // as a ViewDataDictionary<List<int>> holding information about an IEnumerable<int> property (because an // @model directive matched the runtime type though the view's name did not), we'll throw away the property // metadata in those cases -- preserving invariant that ModelType can be assigned to _declaredModelType. // // More generally, since defensive copies to base VDD and VDD<object> abound, it's important to preserve // metadata despite _declaredModelType changes. var modelType = model?.GetType(); var modelOrDeclaredType = modelType ?? declaredModelType; if (source.ModelMetadata.MetadataKind == ModelMetadataKind.Type && source.ModelMetadata.ModelType == typeof(object) && modelOrDeclaredType != typeof(object)) { // Base ModelMetadata on new type when there's no property information to preserve and type changes to // something besides typeof(object). ModelExplorer = _metadataProvider.GetModelExplorerForType(modelOrDeclaredType, model); } else if (!declaredModelType.IsAssignableFrom(source.ModelMetadata.ModelType)) { // Base ModelMetadata on new type when existing metadata is incompatible with the new declared type. ModelExplorer = _metadataProvider.GetModelExplorerForType(modelOrDeclaredType, model); } else if (modelType != null && !source.ModelMetadata.ModelType.IsAssignableFrom(modelType)) { // Base ModelMetadata on new type when new model is incompatible with the existing metadata. ModelExplorer = _metadataProvider.GetModelExplorerForType(modelType, model); } else if (object.ReferenceEquals(model, source.ModelExplorer.Model)) { // Source's ModelExplorer is already exactly correct. ModelExplorer = source.ModelExplorer; } else { // The existing metadata is compatible with the value and declared type but it's a new value. ModelExplorer = new ModelExplorer( _metadataProvider, source.ModelExplorer.Container, source.ModelMetadata, model); } // Ensure the given Model is compatible with _declaredModelType. Do not do this one of the following // special cases: // - Constructing a ViewDataDictionary<TModel> where TModel is a non-Nullable value type. This may for // example occur when activating a RazorPage<int> and the container is null. // - Constructing a ViewDataDictionary<object> immediately before overwriting ModelExplorer with correct // information. See TemplateBuilder.Build(). if (model != null) { EnsureCompatible(model); } }