Example #1
0
        /// <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;
        }
Example #2
0
        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;
        }
Example #3
0
        /// <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;
        }
Example #4
0
        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();
        }
Example #7
0
        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);
        }
Example #8
0
        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);
        }
Example #9
0
        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);
                    }
                }
            }
        }
Example #10
0
        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);
        }
Example #11
0
 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);
 }
Example #15
0
    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));
    }
Example #16
0
        // 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;
            }
        }
Example #17
0
 public IList<ModelExplorer> GetPropertiesForList(ModelExplorer explorer)
 {
     var metadata = explorer.Properties.Where(x => ((DefaultModelMetadata)x.Metadata).Attributes.PropertyAttributes.OfType<ShowListAttribute>().Any());
     return metadata.ToList();
 }
Example #18
0
        /// <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;
        }
Example #19
0
 protected virtual bool IsEnum(ModelExplorer explorer)
 {
     return(explorer.Metadata.IsEnum);
 }
Example #20
0
        // 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;
        }
Example #21
0
        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);
        }
Example #22
0
        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;
        }
Example #23
0
 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;
        }
Example #25
0
 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;
        }
Example #27
0
        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;
            }
        }
Example #29
0
    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);
        }
    }