public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            string input = "";

            string labelCss   = "col-sm-4";
            string controlCss = "col-sm-8";

            var helper = new SelectTagHelper(Generator);

            helper.ViewContext = ViewContext;
            helper.For         = Value;
            helper.Items       = Items;

            helper.Init(context);
            helper.Process(context, output);

            using (var writer = new StringWriter())
            {
                output.TagName = "select";
                output.Attributes.Add("class", "form-control");

                output.WriteTo(writer, HtmlEncoder.Default);
                input = writer.ToString();
            }

            output.Attributes.Clear();
            output.PostContent.Clear();
            output.Content.Clear();

            string req = (input.IndexOf("data-val-required") > -1) ? "<span class='required'>*</span>" : "";

            var label        = Label ?? Value?.Metadata?.DisplayName ?? Value?.Name ?? "";
            var labelContent = $"<label for='{Value.Name}' class='{labelCss} col-form-label'>{req}{label}</label>";

            var validateBuilder = Generator.GenerateValidationMessage(
                ViewContext,
                Value.ModelExplorer,
                Value.Name,
                message: null,
                tag: null,
                htmlAttributes: null);

            var divBuilder = new TagBuilder("div");

            divBuilder.AddCssClass(controlCss);
            divBuilder.InnerHtml.AppendHtml(input);
            divBuilder.InnerHtml.AppendHtml(validateBuilder);

            output.TagName = "div";
            output.Attributes.Add("class", "form-group row");

            output.Content.AppendHtml(labelContent);
            output.Content.AppendHtml(divBuilder);
        }
        /// <summary>
        /// 初始化当前标签上下文。
        /// </summary>
        /// <param name="context">当前HTML标签上下文,包含当前HTML相关信息。</param>
        public override void Init(TagHelperContext context)
        {
            var items = new List <SelectListItem>();

            if (Text != null)
            {
                items.Add(new SelectListItem {
                    Text = Text, Value = Value
                });
            }
            Init(items, context);
            _selectTagHelper.ViewContext = ViewContext;
            _selectTagHelper.Items       = items;
            _selectTagHelper.For         = For;
            _selectTagHelper.Init(context);
        }
    public async Task TagHelper_CallsGeneratorWithExpectedValues_RealModelType(
        Type modelType,
        object model,
        bool allowMultiple)
    {
        // Arrange
        var contextAttributes = new TagHelperAttributeList(
            Enumerable.Empty <TagHelperAttribute>());
        var originalAttributes = new TagHelperAttributeList();
        var propertyName       = "Property1";
        var tagName            = "select";

        var tagHelperContext = new TagHelperContext(
            tagName: "select",
            allAttributes: contextAttributes,
            items: new Dictionary <object, object>(),
            uniqueId: "test");
        var output = new TagHelperOutput(
            tagName,
            originalAttributes,
            getChildContentAsync: (useCachedResult, encoder) =>
        {
            var tagHelperContent = new DefaultTagHelperContent();
            tagHelperContent.SetContent("Something");
            return(Task.FromResult <TagHelperContent>(tagHelperContent));
        });
        var metadataProvider = new EmptyModelMetadataProvider();
        var modelExplorer    = metadataProvider.GetModelExplorerForType(modelType, model);
        var modelExpression  = new ModelExpression(propertyName, modelExplorer);

        var htmlGenerator = new Mock <IHtmlGenerator>(MockBehavior.Strict);
        var viewContext   = TestableHtmlGenerator.GetViewContext(model, htmlGenerator.Object, metadataProvider);
        var currentValues = new string[0];

        htmlGenerator
        .Setup(real => real.GetCurrentValues(
                   viewContext,
                   modelExplorer,
                   propertyName, // expression
                   allowMultiple))
        .Returns(currentValues)
        .Verifiable();
        htmlGenerator
        .Setup(real => real.GenerateSelect(
                   viewContext,
                   modelExplorer,
                   null,         // optionLabel
                   propertyName, // expression
                   It.IsAny <IEnumerable <SelectListItem> >(),
                   currentValues,
                   allowMultiple,
                   null))       // htmlAttributes
        .Returns((TagBuilder)null)
        .Verifiable();

        var tagHelper = new SelectTagHelper(htmlGenerator.Object)
        {
            For         = modelExpression,
            ViewContext = viewContext,
        };

        // Act
        tagHelper.Init(tagHelperContext);
        await tagHelper.ProcessAsync(tagHelperContext, output);

        // Assert
        htmlGenerator.Verify();

        var keyValuePair = Assert.Single(
            tagHelperContext.Items,
            entry => (Type)entry.Key == typeof(SelectTagHelper));
        var actualCurrentValues = Assert.IsType <CurrentValues>(keyValuePair.Value);

        Assert.Same(currentValues, actualCurrentValues.Values);
    }
    public async Task ProcessAsync_CallsGeneratorWithExpectedValues_ItemsAndAttribute(
        IEnumerable <SelectListItem> inputItems,
        string attributeName,
        string attributeValue,
        IEnumerable <SelectListItem> expectedItems)
    {
        // Arrange
        var contextAttributes = new TagHelperAttributeList
        {
            // Provided for completeness. Select tag helper does not confirm AllAttributes set is consistent.
            { attributeName, attributeValue },
        };
        var originalAttributes = new TagHelperAttributeList
        {
            { attributeName, attributeValue },
        };
        var propertyName    = "Property1";
        var expectedTagName = "select";

        var tagHelperContext = new TagHelperContext(
            tagName: "select",
            allAttributes: contextAttributes,
            items: new Dictionary <object, object>(),
            uniqueId: "test");

        var output = new TagHelperOutput(
            expectedTagName,
            originalAttributes,
            getChildContentAsync: (useCachedResult, encoder) =>
        {
            var tagHelperContent = new DefaultTagHelperContent();
            tagHelperContent.SetContent("Something");
            return(Task.FromResult <TagHelperContent>(tagHelperContent));
        });
        var    metadataProvider = new EmptyModelMetadataProvider();
        string model            = null;
        var    modelExplorer    = metadataProvider.GetModelExplorerForType(typeof(string), model);

        var htmlGenerator = new Mock <IHtmlGenerator>(MockBehavior.Strict);
        var viewContext   = TestableHtmlGenerator.GetViewContext(model, htmlGenerator.Object, metadataProvider);

        // Simulate a (model => model) scenario. E.g. the calling helper may appear in a low-level template.
        var modelExpression = new ModelExpression(string.Empty, modelExplorer);

        viewContext.ViewData.TemplateInfo.HtmlFieldPrefix = propertyName;

        var currentValues = new string[0];

        htmlGenerator
        .Setup(real => real.GetCurrentValues(
                   viewContext,
                   modelExplorer,
                   string.Empty, // expression
                   false))       // allowMultiple
        .Returns(currentValues)
        .Verifiable();
        htmlGenerator
        .Setup(real => real.GenerateSelect(
                   viewContext,
                   modelExplorer,
                   null,         // optionLabel
                   string.Empty, // expression
                   expectedItems,
                   currentValues,
                   false,       // allowMultiple
                   null))       // htmlAttributes
        .Returns((TagBuilder)null)
        .Verifiable();

        var tagHelper = new SelectTagHelper(htmlGenerator.Object)
        {
            For         = modelExpression,
            Items       = inputItems,
            ViewContext = viewContext,
        };

        // Act
        tagHelper.Init(tagHelperContext);
        await tagHelper.ProcessAsync(tagHelperContext, output);

        // Assert
        htmlGenerator.Verify();

        var keyValuePair = Assert.Single(
            tagHelperContext.Items,
            entry => (Type)entry.Key == typeof(SelectTagHelper));
        var actualCurrentValues = Assert.IsType <CurrentValues>(keyValuePair.Value);

        Assert.Same(currentValues, actualCurrentValues.Values);
    }
    public async Task ProcessAsyncInTemplate_WithItems_GeneratesExpectedOutput_DoesNotChangeSelectList(
        object model,
        Type containerType,
        Func <object> modelAccessor,
        NameAndId nameAndId,
        string expectedOptions)
    {
        // Arrange
        var originalAttributes = new TagHelperAttributeList
        {
            { "class", "form-control" },
        };
        var originalPostContent = "original content";

        var expectedAttributes = new TagHelperAttributeList(originalAttributes)
        {
            { "id", nameAndId.Id },
            { "name", nameAndId.Name },
            { "valid", "from validation attributes" },
        };
        var expectedPreContent  = "original pre-content";
        var expectedContent     = "original content";
        var expectedPostContent = originalPostContent + expectedOptions;
        var expectedTagName     = "select";

        var metadataProvider = new TestModelMetadataProvider();

        var containerMetadata = metadataProvider.GetMetadataForType(containerType);
        var containerExplorer = metadataProvider.GetModelExplorerForType(containerType, model);

        var propertyMetadata = metadataProvider.GetMetadataForProperty(containerType, "Text");
        var modelExplorer    = containerExplorer.GetExplorerForExpression(propertyMetadata, modelAccessor());

        var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);

        var tagHelperContext = new TagHelperContext(
            tagName: "select",
            allAttributes: new TagHelperAttributeList(
                Enumerable.Empty <TagHelperAttribute>()),
            items: new Dictionary <object, object>(),
            uniqueId: "test");
        var output = new TagHelperOutput(
            expectedTagName,
            originalAttributes,
            getChildContentAsync: (useCachedResult, encoder) =>
        {
            var tagHelperContent = new DefaultTagHelperContent();
            tagHelperContent.AppendHtml("Something");
            return(Task.FromResult <TagHelperContent>(tagHelperContent));
        })
        {
            TagMode = TagMode.SelfClosing,
        };

        output.PreContent.AppendHtml(expectedPreContent);
        output.Content.AppendHtml(expectedContent);
        output.PostContent.AppendHtml(originalPostContent);

        var htmlGenerator = new TestableHtmlGenerator(metadataProvider)
        {
            ValidationAttributes =
            {
                { "valid", "from validation attributes" },
            }
        };
        var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);

        viewContext.ViewData.TemplateInfo.HtmlFieldPrefix = nameAndId.Name;

        var items         = new SelectList(new[] { "", "outer text", "inner text", "other text" });
        var savedDisabled = items.Select(item => item.Disabled).ToList();
        var savedGroup    = items.Select(item => item.Group).ToList();
        var savedSelected = items.Select(item => item.Selected).ToList();
        var savedText     = items.Select(item => item.Text).ToList();
        var savedValue    = items.Select(item => item.Value).ToList();
        var tagHelper     = new SelectTagHelper(htmlGenerator)
        {
            For         = modelExpression,
            Items       = items,
            ViewContext = viewContext,
        };

        // Act
        tagHelper.Init(tagHelperContext);
        await tagHelper.ProcessAsync(tagHelperContext, output);

        // Assert
        Assert.Equal(TagMode.SelfClosing, output.TagMode);
        Assert.Equal(expectedAttributes, output.Attributes);
        Assert.Equal(expectedPreContent, output.PreContent.GetContent());
        Assert.Equal(expectedContent, output.Content.GetContent());
        Assert.Equal(expectedPostContent, HtmlContentUtilities.HtmlContentToString(output.PostContent));
        Assert.Equal(expectedTagName, output.TagName);

        Assert.Single(
            tagHelperContext.Items,
            entry => (Type)entry.Key == typeof(SelectTagHelper));

        Assert.Equal(savedDisabled, items.Select(item => item.Disabled));
        Assert.Equal(savedGroup, items.Select(item => item.Group));
        Assert.Equal(savedSelected, items.Select(item => item.Selected));
        Assert.Equal(savedText, items.Select(item => item.Text));
        Assert.Equal(savedValue, items.Select(item => item.Value));
    }
    public async Task ProcessAsync_WithItems_AndNoModelExpression_GeneratesExpectedOutput()
    {
        // Arrange
        var originalAttributes = new TagHelperAttributeList
        {
            { "class", "form-control" },
        };
        var originalPostContent = "original content";

        var expectedAttributes = new TagHelperAttributeList(originalAttributes);
        var selectItems        = new SelectList(Enumerable.Range(0, 5));
        var expectedOptions    = "<option>HtmlEncode[[0]]</option>" + Environment.NewLine
                                 + "<option>HtmlEncode[[1]]</option>" + Environment.NewLine
                                 + "<option>HtmlEncode[[2]]</option>" + Environment.NewLine
                                 + "<option>HtmlEncode[[3]]</option>" + Environment.NewLine
                                 + "<option>HtmlEncode[[4]]</option>" + Environment.NewLine;

        var expectedPreContent  = "original pre-content";
        var expectedContent     = "original content";
        var expectedPostContent = originalPostContent + expectedOptions;
        var expectedTagName     = "select";

        var tagHelperContext = new TagHelperContext(
            tagName: "select",
            allAttributes: new TagHelperAttributeList(
                Enumerable.Empty <TagHelperAttribute>()),
            items: new Dictionary <object, object>(),
            uniqueId: "test");
        var output = new TagHelperOutput(
            expectedTagName,
            originalAttributes,
            getChildContentAsync: (useCachedResult, encoder) =>
        {
            var tagHelperContent = new DefaultTagHelperContent();
            tagHelperContent.AppendHtml("Something");
            return(Task.FromResult <TagHelperContent>(tagHelperContent));
        })
        {
            TagMode = TagMode.SelfClosing,
        };

        output.PreContent.AppendHtml(expectedPreContent);
        output.Content.AppendHtml(expectedContent);
        output.PostContent.AppendHtml(originalPostContent);

        var metadataProvider = new TestModelMetadataProvider();
        var htmlGenerator    = new TestableHtmlGenerator(metadataProvider);
        var viewContext      = TestableHtmlGenerator.GetViewContext(
            model: null,
            htmlGenerator: htmlGenerator,
            metadataProvider: metadataProvider);

        var tagHelper = new SelectTagHelper(htmlGenerator)
        {
            Items       = selectItems,
            ViewContext = viewContext,
        };

        // Act
        tagHelper.Init(tagHelperContext);
        await tagHelper.ProcessAsync(tagHelperContext, output);

        // Assert
        Assert.Equal(TagMode.SelfClosing, output.TagMode);
        Assert.Equal(expectedAttributes, output.Attributes);
        Assert.Equal(expectedPreContent, output.PreContent.GetContent());
        Assert.Equal(expectedContent, output.Content.GetContent());
        Assert.Equal(expectedPostContent, HtmlContentUtilities.HtmlContentToString(output.PostContent));
        Assert.Equal(expectedTagName, output.TagName);

        var kvp = Assert.Single(tagHelperContext.Items);

        Assert.Equal(typeof(SelectTagHelper), kvp.Key);
        Assert.Null(kvp.Value);
    }
    public async Task ProcessAsync_GeneratesExpectedOutput(
        object model,
        Type containerType,
        Func <object> modelAccessor,
        NameAndId nameAndId,
        string ignored)
    {
        // Arrange
        var originalAttributes = new TagHelperAttributeList
        {
            { "class", "form-control" },
        };
        var originalPostContent = "original content";

        var expectedAttributes = new TagHelperAttributeList(originalAttributes)
        {
            { "id", nameAndId.Id },
            { "name", nameAndId.Name },
            { "valid", "from validation attributes" },
        };
        var expectedPreContent  = "original pre-content";
        var expectedContent     = "original content";
        var expectedPostContent = originalPostContent;
        var expectedTagName     = "not-select";

        var metadataProvider  = new TestModelMetadataProvider();
        var containerMetadata = metadataProvider.GetMetadataForType(containerType);
        var containerExplorer = metadataProvider.GetModelExplorerForType(containerType, model);

        var propertyMetadata = metadataProvider.GetMetadataForProperty(containerType, "Text");
        var modelExplorer    = containerExplorer.GetExplorerForExpression(propertyMetadata, modelAccessor());

        var modelExpression = new ModelExpression(nameAndId.Name, modelExplorer);

        var tagHelperContext = new TagHelperContext(
            tagName: "select",
            allAttributes: new TagHelperAttributeList(
                Enumerable.Empty <TagHelperAttribute>()),
            items: new Dictionary <object, object>(),
            uniqueId: "test");
        var output = new TagHelperOutput(
            expectedTagName,
            originalAttributes,
            getChildContentAsync: (useCachedResult, encoder) =>
        {
            var tagHelperContent = new DefaultTagHelperContent();
            tagHelperContent.SetContent("Something");
            return(Task.FromResult <TagHelperContent>(tagHelperContent));
        })
        {
            TagMode = TagMode.SelfClosing,
        };

        output.PreContent.SetContent(expectedPreContent);
        output.Content.SetContent(expectedContent);
        output.PostContent.SetContent(originalPostContent);

        var htmlGenerator = new TestableHtmlGenerator(metadataProvider)
        {
            ValidationAttributes =
            {
                { "valid", "from validation attributes" },
            }
        };
        var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
        var tagHelper   = new SelectTagHelper(htmlGenerator)
        {
            For         = modelExpression,
            ViewContext = viewContext,
        };

        // Act
        tagHelper.Init(tagHelperContext);
        await tagHelper.ProcessAsync(tagHelperContext, output);

        // Assert
        Assert.Equal(TagMode.SelfClosing, output.TagMode);
        Assert.Equal(expectedAttributes, output.Attributes);
        Assert.Equal(expectedPreContent, output.PreContent.GetContent());
        Assert.Equal(expectedContent, output.Content.GetContent());
        Assert.Equal(expectedPostContent, output.PostContent.GetContent());
        Assert.Equal(expectedTagName, output.TagName);

        Assert.Single(
            tagHelperContext.Items,
            entry => (Type)entry.Key == typeof(SelectTagHelper));
    }