简体   繁体   English

使用数据注释的条件必需属性

[英]Conditionally required property using data annotations

I have a class like this:我有一个像这样的 class :

public class Document
{
   public int DocumentType{get;set;}

   [Required]
   public string Name{get;set;}

   [Required]
   public string Name2{get;set;}
}

Now if I put a [Required] data annotation on the Name and Name2 properties, then everything is ok and if Name or Name2 are empty, validation will throw an error.现在,如果我在NameName2属性上放置[Required]数据注释,那么一切正常,如果NameName2为空,验证将引发错误。

But I want Name field only to be required if DocumentType is equal to 1 and Name2 only required if DocumentType is equal to 2.但是我希望仅在DocumentType等于 1 时才需要Name字段,并且仅在DocumentType等于 2 时才需要Name2

public class Document
{
   public int DocumentType{get;set;}

   [Required(Expression<Func<object, bool>>)]
   public string Name{get;set;}

   [Required(Expression<Func<object, bool>>)]
   public string Name2{get;set;}
}

but I know I can't, it causes an error.但我知道我不能,它会导致错误。 What should I do for this requirement?我应该怎么做这个要求?

RequiredIf validation attribute RequiredIf 验证属性

I've written a RequiredIfAttribute that requires a particular property value when a different property has a certain value (what you require) or when a different property has anything but a specific value.我编写了一个RequiredIfAttribute ,当不同的属性具有特定值(您需要的值)或不同的属性具有特定值以外的任何值时,它需要特定的属性值。

This is the code that may help:这是可能有帮助的代码:

/// <summary>
/// Provides conditional validation based on related property value.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class RequiredIfAttribute : ValidationAttribute
{
    #region Properties

    /// <summary>
    /// Gets or sets the other property name that will be used during validation.
    /// </summary>
    /// <value>
    /// The other property name.
    /// </value>
    public string OtherProperty { get; private set; }

    /// <summary>
    /// Gets or sets the display name of the other property.
    /// </summary>
    /// <value>
    /// The display name of the other property.
    /// </value>
    public string OtherPropertyDisplayName { get; set; }

    /// <summary>
    /// Gets or sets the other property value that will be relevant for validation.
    /// </summary>
    /// <value>
    /// The other property value.
    /// </value>
    public object OtherPropertyValue { get; private set; }

    /// <summary>
    /// Gets or sets a value indicating whether other property's value should match or differ from provided other property's value (default is <c>false</c>).
    /// </summary>
    /// <value>
    ///   <c>true</c> if other property's value validation should be inverted; otherwise, <c>false</c>.
    /// </value>
    /// <remarks>
    /// How this works
    /// - true: validated property is required when other property doesn't equal provided value
    /// - false: validated property is required when other property matches provided value
    /// </remarks>
    public bool IsInverted { get; set; }

    /// <summary>
    /// Gets a value that indicates whether the attribute requires validation context.
    /// </summary>
    /// <returns><c>true</c> if the attribute requires validation context; otherwise, <c>false</c>.</returns>
    public override bool RequiresValidationContext
    {
        get { return true; }
    }

    #endregion

    #region Constructor

    /// <summary>
    /// Initializes a new instance of the <see cref="RequiredIfAttribute"/> class.
    /// </summary>
    /// <param name="otherProperty">The other property.</param>
    /// <param name="otherPropertyValue">The other property value.</param>
    public RequiredIfAttribute(string otherProperty, object otherPropertyValue)
        : base("'{0}' is required because '{1}' has a value {3}'{2}'.")
    {
        this.OtherProperty = otherProperty;
        this.OtherPropertyValue = otherPropertyValue;
        this.IsInverted = false;
    }

    #endregion

    /// <summary>
    /// Applies formatting to an error message, based on the data field where the error occurred.
    /// </summary>
    /// <param name="name">The name to include in the formatted message.</param>
    /// <returns>
    /// An instance of the formatted error message.
    /// </returns>
    public override string FormatErrorMessage(string name)
    {
        return string.Format(
            CultureInfo.CurrentCulture,
            base.ErrorMessageString,
            name,
            this.OtherPropertyDisplayName ?? this.OtherProperty,
            this.OtherPropertyValue,
            this.IsInverted ? "other than " : "of ");
    }

    /// <summary>
    /// Validates the specified value with respect to the current validation attribute.
    /// </summary>
    /// <param name="value">The value to validate.</param>
    /// <param name="validationContext">The context information about the validation operation.</param>
    /// <returns>
    /// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult" /> class.
    /// </returns>
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (validationContext == null)
        {
            throw new ArgumentNullException("validationContext");
        }

        PropertyInfo otherProperty = validationContext.ObjectType.GetProperty(this.OtherProperty);
        if (otherProperty == null)
        {
            return new ValidationResult(
                string.Format(CultureInfo.CurrentCulture, "Could not find a property named '{0}'.", this.OtherProperty));
        }

        object otherValue = otherProperty.GetValue(validationContext.ObjectInstance);

        // check if this value is actually required and validate it
        if (!this.IsInverted && object.Equals(otherValue, this.OtherPropertyValue) ||
            this.IsInverted && !object.Equals(otherValue, this.OtherPropertyValue))
        {
            if (value == null)
            {
                return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
            }

            // additional check for strings so they're not empty
            string val = value as string;
            if (val != null && val.Trim().Length == 0)
            {
                return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
            }
        }

        return ValidationResult.Success;
    }
}

Conditionally required property using data annotations使用数据注释的有条件要求的属性

 [RequiredIf(dependent Property name, dependent Property value)]

e.g. 


 [RequiredIf("Country", "Ethiopia")]
 public string POBox{get;set;}
 // POBox is required in Ethiopia
 public string Country{get;set;}

 [RequiredIf("destination", "US")]
 public string State{get;set;}
 // State is required in US

 public string destination{get;set;}



public class RequiredIfAttribute : ValidationAttribute
{
    RequiredAttribute _innerAttribute = new RequiredAttribute();
    public string _dependentProperty { get; set; }
    public object _targetValue { get; set; }

    public RequiredIfAttribute(string dependentProperty, object targetValue)
    {
        this._dependentProperty = dependentProperty;
        this._targetValue = targetValue;
    }
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var field = validationContext.ObjectType.GetProperty(_dependentProperty);
        if (field != null)
        {
            var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
            if ((dependentValue == null && _targetValue == null) || (dependentValue.Equals(_targetValue)))
            {
                if (!_innerAttribute.IsValid(value))
                {
                    string name = validationContext.DisplayName;
                    return new ValidationResult(ErrorMessage=name + " Is required.");
                }
            }
            return ValidationResult.Success;
        }
        else
        {
            return new ValidationResult(FormatErrorMessage(_dependentProperty));
        }
    }
}

Out of the box I think this is still not possible.开箱即用,我认为这仍然是不可能的。

But I found this promising article about Mvc.ValidationToolkit (also here , unfortunately this is only alpha, but you probably could also just extract the method(s) you need from this code and integrate it on your own) , it contains the nice sounding attribute RequiredIf which seems to match exactly your cause:但是我发现了这篇关于 Mvc.ValidationToolkit 的有前途的文章(也在这里,不幸的是只是 alpha,但你可能也可以从这段代码中提取你需要的方法并自己集成它) ,它包含了很好的声音属性RequiredIf似乎与您的原因完全匹配:

  • you download the project from the linked zip and build it您从链接的 zip下载项目并构建它
  • get the built dll from your build folder and reference it in the project you are using从您的构建文件夹中获取构建的 dll 并在您正在使用的项目中引用它
  • unfortunately this seems to require reference to MVC, too (easiest way to have that is starting an MVC-Project in VS or install-package Microsoft.AspNet.Mvc )不幸的是,这似乎也需要引用 MVC(最简单的方法是在 VS 中启动 MVC-Project 或install-package Microsoft.AspNet.Mvc
  • in the files where you want to use it, you add using Mvc.ValidationToolkit;在要使用它的文件中, using Mvc.ValidationToolkit;添加using Mvc.ValidationToolkit;
  • then you are able to write things like [RequiredIf("DocumentType", 2)] or [RequiredIf("DocumentType", 1)] , so objects are valid if neither name or name2 are supplied as long as DocumentType is not equal to 1 or 2那么您就可以编写诸如[RequiredIf("DocumentType", 2)][RequiredIf("DocumentType", 1)] ,因此只要DocumentType不等于 1,如果namename2均未提供,则对象是有效的或 2

Check out Fluent Validation查看 Fluent 验证

https://www.nuget.org/packages/FluentValidation/ https://www.nuget.org/packages/FluentValidation/

Project Description A small validation library for .NET that uses a fluent interface and lambda expressions for building validation rules for your business objects.项目描述 .NET 的小型验证库,它使用流畅的接口和 lambda 表达式为您的业务对象构建验证规则。

https://github.com/JeremySkinner/FluentValidation https://github.com/JeremySkinner/FluentValidation

I have always used implemented IValidatableObject from System.ComponentModel.DataAnnotations;我一直使用 System.ComponentModel.DataAnnotations 中实现的 IValidatableObject;

Example below下面的例子

  public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            if (this.SendInAppNotification)
            {
                if (string.IsNullOrEmpty(this.NotificationTitle) || string.IsNullOrWhiteSpace(this.NotificationTitle))
                {
                    yield return new ValidationResult(
                        $"Notification Title is required",
                        new[] { nameof(this.NotificationTitle) });
                }
            }

Check out MVC Foolproof validation.查看MVC万无一失的验证。 It has data annotation in model like RequiredIf (dependent Property, dependent value) if I remember correctly.如果我没记错的话它在模型中有数据注释,比如RequiredIf(依赖属性,依赖值) You can download Foolproof from:您可以从以下位置下载万无一失:
Visual Studio(2017) -> Tools -> Nuget Package Manager -> Manage Nuget Packages for Solution. Visual Studio(2017) -> 工具 -> Nuget 包管理器 -> 管理解决方案的 Nuget 包。 Reference mvcfoolproof.unobtrusive.min.js in addition to the jquery files.除了 jquery 文件之外,还参考 mvcfoolproof.unobtrusive.min.js。

check out the ExpressiveAnnotations .net library Git reference查看 ExpressiveAnnotations .net 库Git 参考

It has 'RequiredIf' and 'AssertThat' validation attributes它具有“RequiredIf”和“AssertThat”验证属性

I solved this by extending the RequiredAttribute class, borrowing some logic from the CompareAttribute and Robert's excellent solution :我通过扩展RequiredAttribute类解决了这个问题,从CompareAttributeRobert 的优秀解决方案中借用了一些逻辑:

/// <summary>
/// Provides conditional <see cref="RequiredAttribute"/> 
/// validation based on related property value.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class RequiredIfAttribute : RequiredAttribute
{
    /// <summary>
    /// Gets or sets a value indicating whether other property's value should
    /// match or differ from provided other property's value (default is <c>false</c>).
    /// </summary>
    public bool IsInverted { get; set; } = false;

    /// <summary>
    /// Gets or sets the other property name that will be used during validation.
    /// </summary>
    /// <value>
    /// The other property name.
    /// </value>
    public string OtherProperty { get; private set; }

    /// <summary>
    /// Gets or sets the other property value that will be relevant for validation.
    /// </summary>
    /// <value>
    /// The other property value.
    /// </value>
    public object OtherPropertyValue { get; private set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="RequiredIfAttribute"/> class.
    /// </summary>
    /// <param name="otherProperty">The other property.</param>
    /// <param name="otherPropertyValue">The other property value.</param>
    public RequiredIfAttribute(string otherProperty, object otherPropertyValue)
        : base()
    {
        OtherProperty = otherProperty;
        OtherPropertyValue = otherPropertyValue;
    }

    protected override ValidationResult IsValid(
        object value, 
        ValidationContext validationContext)
    {
        PropertyInfo otherPropertyInfo = validationContext
            .ObjectType.GetProperty(OtherProperty);
        if (otherPropertyInfo == null)
        {
            return new ValidationResult(
                string.Format(
                    CultureInfo.CurrentCulture, 
                    "Could not find a property named {0}.", 
                validationContext.ObjectType, OtherProperty));
        }

        // Determine whether to run [Required] validation
        object actualOtherPropertyValue = otherPropertyInfo
            .GetValue(validationContext.ObjectInstance, null);
        if (!IsInverted && Equals(actualOtherPropertyValue, OtherPropertyValue) ||
            IsInverted && !Equals(actualOtherPropertyValue, OtherPropertyValue))
        {
            return base.IsValid(value, validationContext);
        }
        return default;
    }
}

Example usage:用法示例:

public class Model {
    public bool Subscribe { get; set; }
    
    [RequiredIf(nameof(Subscribe), true)]
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }
}

This way, you get all the standard Required validation features.通过这种方式,您可以获得所有标准的Required验证功能。

NB: I am using .NET 5, but I tried to remove language features added in c# 9.0 for wider compatibility.注意:我使用的是 .NET 5,但我试图删除在 c# 9.0 中添加的语言功能以获得更广泛的兼容性。

I know this question is from a long time ago but someone asked in the comments section of Robert's answer how to use unobtrusive as part of the solution.我知道这个问题来自很久以前,但有人在罗伯特回答的评论部分询问如何使用不引人注目作为解决方案的一部分。

I wanted client side validation as well so I'm sharing my revised code to Robert's original code.我也想要客户端验证,所以我将修改后的代码分享给 Robert 的原始代码。 It's essentially the same code except it implements IClientModelValidator and has an additional AddValidation method.除了实现IClientModelValidator并具有附加的AddValidation方法之外,它基本上是相同的代码。 The client validation still respects the IsInverted property.客户端验证仍然尊重 IsInverted 属性。

Implement IClientModelValidator实现 IClientModelValidator

public sealed class RequiredIfAttribute : ValidationAttribute, IClientModelValidator

New AddValidation method新的 AddValidation 方法

 public void AddValidation(ClientModelValidationContext context)
        {
            var viewContext = context.ActionContext as ViewContext;
            var modelType = context.ModelMetadata.ContainerType;
            var instance = viewContext?.ViewData.Model;
            var model = instance?.GetType().Name == modelType.Name
                ? instance
                : instance?.GetType()?.GetProperties().First(x => x.PropertyType.Name == modelType.Name)
                    .GetValue(instance, null);
            object otherValue = modelType.GetProperty(this.OtherProperty)?.GetValue(model, null);
            object value = modelType.GetProperty(context.ModelMetadata.Name)?.GetValue(model, null);
            string displayName = context.ModelMetadata.DisplayName ?? context.ModelMetadata.Name;
            string errorMessage = null;

            // check if this value is actually required and validate it
            if (!this.IsInverted && object.Equals(otherValue, this.OtherPropertyValue) ||
                this.IsInverted && !object.Equals(otherValue, this.OtherPropertyValue))
            {
                if (value == null)
                {
                    errorMessage = this.FormatErrorMessage(displayName);
                }

                // additional check for strings so they're not empty
                string val = value as string;
                if (val != null && val.Trim().Length == 0)
                {
                    errorMessage = this.FormatErrorMessage(displayName);
                }
            }

            if (!string.IsNullOrWhiteSpace(errorMessage))
            {
                context.Attributes.Add("data-val", "true");
                context.Attributes.Add("data-val-required", errorMessage);
            }
            
        }

Full Code完整代码

    /// <summary>
    /// Provides conditional validation based on related property value.
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public sealed class RequiredIfAttribute : ValidationAttribute, IClientModelValidator
    {
        #region Properties

        /// <summary>
        /// Gets or sets the other property name that will be used during validation.
        /// </summary>
        /// <value>
        /// The other property name.
        /// </value>
        public string OtherProperty { get; private set; }

        /// <summary>
        /// Gets or sets the display name of the other property.
        /// </summary>
        /// <value>
        /// The display name of the other property.
        /// </value>
        public string OtherPropertyDisplayName { get; set; }

        /// <summary>
        /// Gets or sets the other property value that will be relevant for validation.
        /// </summary>
        /// <value>
        /// The other property value.
        /// </value>
        public object OtherPropertyValue { get; private set; }

        /// <summary>
        /// Gets or sets a value indicating whether other property's value should match or differ from provided other property's value (default is <c>false</c>).
        /// </summary>
        /// <value>
        ///   <c>true</c> if other property's value validation should be inverted; otherwise, <c>false</c>.
        /// </value>
        /// <remarks>
        /// How this works
        /// - true: validated property is required when other property doesn't equal provided value
        /// - false: validated property is required when other property matches provided value
        /// </remarks>
        public bool IsInverted { get; set; }

        /// <summary>
        /// Gets a value that indicates whether the attribute requires validation context.
        /// </summary>
        /// <returns><c>true</c> if the attribute requires validation context; otherwise, <c>false</c>.</returns>
        public override bool RequiresValidationContext
        {
            get { return true; }
        }
        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="RequiredIfAttribute"/> class.
        /// </summary>
        /// <param name="otherProperty">The other property.</param>
        /// <param name="otherPropertyValue">The other property value.</param>
        public RequiredIfAttribute(string otherProperty, object otherPropertyValue)
            : base("'{0}' is required because '{1}' has a value {3}'{2}'.")
        {
            this.OtherProperty = otherProperty;
            this.OtherPropertyValue = otherPropertyValue;
            this.IsInverted = false;
        }

        #endregion

        public void AddValidation(ClientModelValidationContext context)
        {
            var viewContext = context.ActionContext as ViewContext;
            var modelType = context.ModelMetadata.ContainerType;
            var instance = viewContext?.ViewData.Model;
            var model = instance?.GetType().Name == modelType.Name
                ? instance
                : instance?.GetType()?.GetProperties().First(x => x.PropertyType.Name == modelType.Name)
                    .GetValue(instance, null);
            object otherValue = modelType.GetProperty(this.OtherProperty)?.GetValue(model, null);
            object value = modelType.GetProperty(context.ModelMetadata.Name)?.GetValue(model, null);
            string displayName = context.ModelMetadata.DisplayName ?? context.ModelMetadata.Name;
            string errorMessage = null;

            // check if this value is actually required and validate it
            if (!this.IsInverted && object.Equals(otherValue, this.OtherPropertyValue) ||
                this.IsInverted && !object.Equals(otherValue, this.OtherPropertyValue))
            {
                if (value == null)
                {
                    errorMessage = this.FormatErrorMessage(displayName);
                }

                // additional check for strings so they're not empty
                string val = value as string;
                if (val != null && val.Trim().Length == 0)
                {
                    errorMessage = this.FormatErrorMessage(displayName);
                }
            }

            if (!string.IsNullOrWhiteSpace(errorMessage))
            {
                context.Attributes.Add("data-val", "true");
                context.Attributes.Add("data-val-required", errorMessage);
            }
            
        }

        /// <summary>
        /// Applies formatting to an error message, based on the data field where the error occurred.
        /// </summary>
        /// <param name="name">The name to include in the formatted message.</param>
        /// <returns>
        /// An instance of the formatted error message.
        /// </returns>
        public override string FormatErrorMessage(string name)
        {
            return string.Format(
                CultureInfo.CurrentCulture,
                base.ErrorMessageString,
                name,
                this.OtherPropertyDisplayName ?? this.OtherProperty,
                this.OtherPropertyValue,
                this.IsInverted ? "other than " : "of ");
        }

        /// <summary>
        /// Validates the specified value with respect to the current validation attribute.
        /// </summary>
        /// <param name="value">The value to validate.</param>
        /// <param name="validationContext">The context information about the validation operation.</param>
        /// <returns>
        /// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult" /> class.
        /// </returns>
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            if (validationContext == null)
            {
                throw new ArgumentNullException("validationContext");
            }

            PropertyInfo otherProperty = validationContext.ObjectType.GetProperty(this.OtherProperty);
            if (otherProperty == null)
            {
                return new ValidationResult(
                    string.Format(CultureInfo.CurrentCulture, "Could not find a property named '{0}'.", this.OtherProperty));
            }

            object otherValue = otherProperty.GetValue(validationContext.ObjectInstance);

            // check if this value is actually required and validate it
            if (!this.IsInverted && object.Equals(otherValue, this.OtherPropertyValue) ||
                this.IsInverted && !object.Equals(otherValue, this.OtherPropertyValue))
            {
                if (value == null)
                {
                    return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
                }

                // additional check for strings so they're not empty
                string val = value as string;
                if (val != null && val.Trim().Length == 0)
                {
                    return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
                }
            }

            return ValidationResult.Success;
        }
    }

This should just work, provided you have included jquery.js, jquery.validate.js and jquery.validate.unobtrusive.js script files (in that order) to your layout or razor view.如果您已将 jquery.js、jquery.validate.js 和 jquery.validate.unobtrusive.js 脚本文件(按此顺序)包含到您的布局或 razor 视图中,这应该可以正常工作。

I wrote a simple custom validation attribute that it's very readable.我写了一个简单的自定义验证属性,它非常易读。

using System;
using System.ComponentModel.DataAnnotations;

namespace some.namespace
{
    public class RequiredIfAttribute : ValidationAttribute
    {
        public string PropertyName { get; set; }
        public object Value { get; set; }

        public RequiredIfAttribute(string propertyName, object value = null, string errorMessage = "")
        {
            PropertyName = propertyName;
            Value = value;
            ErrorMessage = errorMessage;
        }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            if (PropertyName == null || PropertyName.ToString() == "")
            {
                throw new Exception("RequiredIf: you have to indicate the name of the property to use in the validation");
            }

            var propertyValue = GetPropertyValue(validationContext);

            if (HasPropertyValue(propertyValue) && (value == null || value.ToString() == ""))
            {
                return new ValidationResult(ErrorMessage);
            }
            else
            {
                return ValidationResult.Success;
            }
        }

        private object GetPropertyValue(ValidationContext validationContext)
        {
            var instance = validationContext.ObjectInstance;
            var type = instance.GetType();
            return type.GetProperty(PropertyName).GetValue(instance);
        }

        private bool HasPropertyValue(object propertyValue)
        {
            if (Value != null)
            {
                return propertyValue != null && propertyValue.ToString() == Value.ToString();
            }
            else
            {
                return propertyValue != null && propertyValue.ToString() != "";
            }
        }
    }
}

You can use it like this你可以像这样使用它

public class Document
{
   public int DocumentType{get;set;}

   [RequiredIf("DocumentType", "1", ErrorMessage = "The field is required.")]
   public string Name{get;set;}

   [RequiredIf("DocumentType", "2", ErrorMessage = "The field is required.")]
   public string Name2{get;set;}
}

I can't give you exactly what you're asking for, but have you considered something like the following?我不能完全给你你所要求的,但你有没有考虑过以下内容?

public abstract class Document // or interface, whichever is appropriate for you
{
    //some non-validted common properties
}

public class ValidatedDocument : Document
{
    [Required]
    public string Name {get;set;}
}

public class AnotherValidatedDocument : Document
{
    [Required]
    public string Name {get;set;}

    //I would suggest finding a descriptive name for this instead of Name2, 
    //Name2 doesn't make it clear what it's for
    public string Name2 {get;set;}
}

public class NonValidatedDocument : Document
{
    public string Name {get;set;}
}

//Etc...

Justification being the int DocumentType variable.理由是int DocumentType变量。 You could replace this with using concrete subclass types for each "type" of document you need to deal with.您可以将其替换为为您需要处理的每种“类型”文档使用具体的子类类型。 Doing this gives you much better control of your property annotations.这样做可以让您更好地控制您的属性注释。

It also appears that only some of your properties are needed in different situations, which could be a sign that your document class is trying to do too much, and supports the suggestion above.似乎在不同情况下只需要您的某些属性,这可能表明您的文档类试图做太多事情,并支持上述建议。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM