2

JQGridAdvanceSearch機能を使用していますmultipleSearch: true, multipleGroup: true

Asp.netMVCと従来のado.net+ストアドプロシージャも使用しています。

ユーザーがJGRIDでデータを検索するときはいつでも、この検索基準をパラメーター値としてストアドプロシージャに渡します。そのような ...

Select * 
From tableName 
Where @WhereClauseDynamic

そこで、「WhereClauseGenerator」クラスを作成しました。

[ModelBinder(typeof(GridModelBinder))]
public class JqGrid_Setting_VewModel
{
    public bool IsSearch { get; set; }
    public int PageSize { get; set; }
    public int PageIndex { get; set; }
    public string SortColumn { get; set; }
    public string SortOrder { get; set; }
    public string Where { get; set; }
}

public class WhereClauseGenerator
{
    private static readonly string[] FormatMapping = {
        " ({0} = '{1}') ",               // "eq" - equal
        " ({0} <> {1}) ",                // "ne" - not equal
        " ({0} < {1}) ",                 // "lt" - less than
        " ({0} <= {1}) ",                // "le" - less than or equal to
        " ({0} > {1}) ",                 // "gt" - greater than
        " ({0} >= {1}) ",                // "ge" - greater than or equal to
        " ({0} LIKE '{1}%') ",           // "bw" - begins with
        " ({0} NOT LIKE '{1}%') ",       // "bn" - does not begin with
        " ({0} LIKE '%{1}') ",           // "ew" - ends with
        " ({0} NOT LIKE '%{1}') ",       // "en" - does not end with
        " ({0} LIKE '%{1}%') ",          // "cn" - contains
        " ({0} NOT LIKE '%{1}%') "       // "nc" - does not contain
    };

    public string Generator(Filter _Filter)
    {
        var sb = new StringBuilder();            

        foreach (Rule rule in _Filter.rules)
        {
            if (sb.Length != 0)
                sb.Append(_Filter.groupOp);

            sb.AppendFormat(FormatMapping[(int)rule.op], rule.field, rule.data);
        }

        return sb.ToString();
    }
}

public class GridModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        try
        {
            var request = controllerContext.HttpContext.Request;
            var serializer = new JavaScriptSerializer();
            var _WhereClauseGenerator = new WhereClauseGenerator();

            var _IsSearch = bool.Parse(request["_search"] ?? "false");
            var _PageIndex = int.Parse(request["page"] ?? "1");
            var _PageSize = int.Parse(request["rows"] ?? "10");
            var _SortColumn = request["sidx"] ?? "";
            var _SortOrder = request["sord"] ?? "asc";
            var _Where = request["filters"] ?? "";

            return new JqGrid_Setting_VewModel
            {
                IsSearch = _IsSearch,
                PageIndex = _PageIndex,
                PageSize = _PageSize,
                SortColumn = _SortColumn,
                SortOrder = _SortOrder,
                Where = (_IsSearch == false || string.IsNullOrEmpty(_Where)) ? string.Empty : _WhereClauseGenerator.Generator(serializer.Deserialize<Filter>(_Where))
            };

        }
        catch
        {
            return null;
        }
    }
}

[DataContract]
public class Filter
{
    [DataMember]
    public GroupOp groupOp { get; set; }
    [DataMember]
    public List<Rule> rules { get; set; }
}

[DataContract]
public class Rule
{
    [DataMember]
    public string field { get; set; }
    [DataMember]
    public Operations op { get; set; }
    [DataMember]
    public string data { get; set; }
}

public enum GroupOp
{
    AND,
    OR
}

public enum Operations
{
    eq, // "equal"
    ne, // "not equal"
    lt, // "less"
    le, // "less or equal"
    gt, // "greater"
    ge, // "greater or equal"
    bw, // "begins with"
    bn, // "does not begin with"
    //in, // "in"
    //ni, // "not in"
    ew, // "ends with"
    en, // "does not end with"
    cn, // "contains"
    nc  // "does not contain"
}

上位のコードを使用することで、そのように検索するとすべてが正しくなります

{
"groupOp":"AND",
"rules":[{"field":"Seminar_Code","op":"eq","data":"MED01"},
         {"field":"Seminar_Code","op":"eq","data":"CMP05"}],"groups":[]      
}

 sb.ToString() // Output vlaue
 " (Seminar_Code = 'MED01') AND (Seminar_Code = 'CMP05') "

だから、それは完全に正しいです。

しかし、そのようなより複雑な検索クエリになると...

{
"groupOp":"AND",
"rules":[{"field":"Seminar_Code","op":"eq","data":"MED01"},
     {"field":"Seminar_Code","op":"eq","data":"CMP05"}],

     "groups":[{
            "groupOp":"OR",
            "rules": [{"field":"Seminar_Code","op":"eq","data":"CMP01"}],"groups":[]}]              
}

sb.ToString() // Actual Output value is like that below
" (Seminar_Code = 'MED01') AND (Seminar_Code = 'CMP05') "

しかし、私が期待していたのは、以下のようなものです。

" ((Seminar_Code = 'MED01') AND (Seminar_Code = 'CMP05')) OR ( Seminar_Code = 'CMP01' ) "

では、どうすれば正しく行うことができますか?

JQGridは「AND」+「OR」のような複数のグループ操作をサポートしていますか?これは同時に1人のオペレーターのみをサポートしますか?「AND」と「OR」の演算子を同時に使用できますか?

すべての提案をいただければ幸いです。

4

1 に答える 1

6

まず第一に、あなたが使っているコードは危険だと思います。SELECT で使用する WHERE 構造を構築し、入力データを信頼します。SQL インジェクションの問題を受け取ることができます。コードをもっと安全に書くべきです。すべてをエスケープする必要があり[、を含む演算子で使用され%ます。_LIKE

さらに、パラメーターで SELECT を使用することをお勧めします。それ以外の

Seminar_Code LIKE 'MED01%'

あなたが使用することができます

Seminar_Code LIKE (@p1 + '%')

SqlCommand.Parametersを使用して、使用@p1するその他のパラメーターの値を定義します。

今、私はあなたの主な質問に答えようとしています. 使用するクラスの定義は、入力の一部Filterを使用しません。クラスを次のようにgroups拡張する必要がありますFilter

public class Filter {
    public GroupOp groupOp { get; set; }
    public List<Rule> rules { get; set; }
    public List<Filter> groups { get; set; }
}

また、メソッドのコードを拡張してパーツWhereClauseGenerator.Generatorを分析する必要がありgroupsます。さらに、標準の名前変換に近い名前を使用することをお勧めします。クラスのプライベートメンバーではなく、変数のような名前を使用する_Filterと、コードが誤解されやすくなります。さらに、クラスWhereClauseGeneratorは static ( public static class WhereClauseGenerator) であり、メソッドGeneratorも同様です。

groupsの部分のサポートを追加する最も単純なコードは次のとおりですFilter

public static string Generator (Filter filters) {
    var sb = new StringBuilder ();

    if (filters.groups != null && filters.groups.Count > 0)
        sb.Append (" (");

    bool firstRule = true;
    if (filters.rules != null) {
        foreach (var rule in filters.rules) {
            if (!firstRule)
                sb.Append (filters.groupOp);
            else
                firstRule = false;

            sb.AppendFormat (FormatMapping[(int)rule.op], rule.field, rule.data);
        }
    }

    if (filters.groups != null && filters.groups.Count > 0) {
        sb.Append (") ");

        foreach (var filter in filters.groups) {
            if (sb.Length != 0)
                sb.Append (filter.groupOp);
            sb.Append (" (");
            sb.Append (Generator (filter));
            sb.Append (") ");
        }
    }

    return sb.ToString ();
}

更新:より洗練されたfilters入力に対して正しい結果を生成するには、上記のコードを変更する必要があります。

public class Filter {
    public GroupOp groupOp { get; set; }
    public List<Rule> rules { get; set; }
    public List<Filter> groups { get; set; }
}

public class Rule {
    public string field { get; set; }
    public Operations op { get; set; }
    public string data { get; set; }
}

public enum GroupOp {
    AND,
    OR
}

public enum Operations {
    eq, // "equal"
    ne, // "not equal"
    lt, // "less"
    le, // "less or equal"
    gt, // "greater"
    ge, // "greater or equal"
    bw, // "begins with"
    bn, // "does not begin with"
    //in, // "in"
    //ni, // "not in"
    ew, // "ends with"
    en, // "does not end with"
    cn, // "contains"
    nc  // "does not contain"
}

public static class WhereClauseGenerator {
    private static readonly string[] FormatMapping = {
        "({0} = '{1}')",               // "eq" - equal
        "({0} <> {1})",                // "ne" - not equal
        "({0} < {1})",                 // "lt" - less than
        "({0} <= {1})",                // "le" - less than or equal to
        "({0} > {1})",                 // "gt" - greater than
        "({0} >= {1})",                // "ge" - greater than or equal to
        "({0} LIKE '{1}%')",           // "bw" - begins with
        "({0} NOT LIKE '{1}%')",       // "bn" - does not begin with
        "({0} LIKE '%{1}')",           // "ew" - ends with
        "({0} NOT LIKE '%{1}')",       // "en" - does not end with
        "({0} LIKE '%{1}%')",          // "cn" - contains
        "({0} NOT LIKE '%{1}%')"       // "nc" - does not contain
    };

    private static StringBuilder ParseRule(ICollection<Rule> rules, GroupOp groupOp) {
        if (rules == null || rules.Count == 0)
            return null;

        var sb = new StringBuilder ();
        bool firstRule = true;
        foreach (var rule in rules) {
            if (!firstRule)
                // skip groupOp before the first rule
                sb.Append (groupOp);
            else
                firstRule = false;

            sb.AppendFormat (FormatMapping[(int)rule.op], rule.field, rule.data);
        }
        return sb.Length > 0 ? sb : null;
    }

    private static void AppendWithBrackets (StringBuilder dest, StringBuilder src) {
        if (src == null || src.Length == 0)
            return;

        if (src.Length > 2 && src[0] != '(' && src[src.Length - 1] != ')') {
            dest.Append ('(');
            dest.Append (src);
            dest.Append (')');
        } else {
            // verify that no other '(' and ')' exist in the b. so that
            // we have no case like src = "(x < 0) OR (y > 0)"
            for (int i = 1; i < src.Length - 1; i++) {
                if (src[i] == '(' || src[i] == ')') {
                    dest.Append ('(');
                    dest.Append (src);
                    dest.Append (')');
                    return;
                }
            }
            dest.Append (src);
        }
    }

    private static StringBuilder ParseFilter(ICollection<Filter> groups, GroupOp groupOp) {
        if (groups == null || groups.Count == 0)
            return null;

        var sb = new StringBuilder ();
        bool firstGroup = true;
        foreach (var group in groups) {
            var sbGroup = ParseFilter(group);
            if (sbGroup == null || sbGroup.Length == 0)
                continue;

            if (!firstGroup)
                // skip groupOp before the first group
                sb.Append (groupOp);
            else
                firstGroup = false;

            sb.EnsureCapacity (sb.Length + sbGroup.Length + 2);
            AppendWithBrackets (sb, sbGroup);
        }
        return sb;
    }

    public static StringBuilder ParseFilter(Filter filters) {
        var parsedRules = ParseRule (filters.rules, filters.groupOp);
        var parsedGroups = ParseFilter (filters.groups, filters.groupOp);

        if (parsedRules != null && parsedRules.Length > 0) {
            if (parsedGroups != null && parsedGroups.Length > 0) {
                var groupOpStr = filters.groupOp.ToString();
                var sb = new StringBuilder (parsedRules.Length + parsedGroups.Length + groupOpStr.Length + 4);
                AppendWithBrackets (sb, parsedRules);
                sb.Append (groupOpStr);
                AppendWithBrackets (sb, parsedGroups);
                return sb;
            }
            return parsedRules;
        }
        return parsedGroups;
    }
}

これで、次のようなParseFilter静的クラスの静的メソッドを使用できますWhereClauseGenerator

var filters = request["filters"];
string whereString = request["_search"] && !String.IsNullOrEmpty(filters)
    ? WhereClauseGenerator.ParseFilter(serializer.Deserialize<Filter>(filters))
    : String.Empty;

SQL インジェクションの問題がまだ存在することを忘れないでください。どの種類のデータベース アクセスを使用しているかがわからなくなるまで、修正できません。

于 2012-04-07T07:42:47.457 に答える