【问题标题】:Use of SqlDataSource From Non-Control Situations在非控制情况下使用 SqlDataSource
【发布时间】:2013-12-18 21:26:57
【问题描述】:

作为我在所有业务应用程序中使用的常用实用程序的一部分,我有这段代码...

using System.Web.UI.WebControls;

public class Database
    {
    /// <summary>
    /// Creates a DataView object using the provided query and an SqlDataSource object.
    /// </summary>
    /// <param name="query">The select command to perform.</param>
    /// <returns>A DataView with data results from executing the query.</returns>
    public static DataView GetDataView(string query)
        {
        SqlDataSource ds = GetDBConnection();
        ds.SelectCommand = query;
        DataView dv = (DataView)ds.Select(DataSourceSelectArguments.Empty);
        return dv;
        }

     /// <summary>
     /// Creates a SqlDataSource object with initialized connection string and provider
     /// </summary>
    /// <returns>An SqlDataSource that has been initialized.</returns>
    public static SqlDataSource GetDBConnection()
        {
        SqlDataSource db = new SqlDataSource();
        db.ConnectionString = GetDefaultConnectionString(); //retrieves connection string from .config file
        db.ProviderName = GetDefaultProviderName(); //retrieves provider name from .config file
        return db;
        }
   }

然后,在我的项目中,为了从数据库中检索数据,我将编写一些代码,例如..

DataView dv=Database.GetDataView("select mycolumn from my table");
//loop through data and make use of it

我因以这种方式使用 SqlDataSource 而受到了人们的关注。人们似乎不喜欢我纯粹从代码中使用 Web 控件,而不是将其放在 ASPX 页面上。这对他们来说看起来不对,但他们无法告诉我不利的一面。那么,有缺点吗?这是我的主要问题。因为如果有很多缺点,我可能不得不改变我开发许多内部应用程序的方式。

只要我添加 System.Web 程序集,我的 Database 类甚至可以在非 ASP.NET 情况下工作。我知道包大小略有增加,但我觉得对于我正在编写的应用程序类型来说这是值得的。从 WPF/Windows 窗体/控制台程序中使用 SqlDataSource 是否有缺点?

【问题讨论】:

  • 此代码是您真实代码的简化版本,还是您从不向查询传递参数?因为你贴出来的代码好像是邀请SQL Injection如果需要传参数的话。
  • @RichardDeeming Simplified,我还有其他代码可以清理 SQL 注入。我想我应该提一下,这段代码用于我们 60 人公司的内部业务线应用程序。它不公开。
  • 内部用户不可能试图破解您的数据库或恶意软件进入您的网络,对吧? troyhunt.com/2013/10/your-corporate-network-is-already.html
  • @RichardDeeming 我并不是说不可能。我是说我会采取措施防范它,而且它不像公共网站那样令人担忧。

标签: c# asp.net .net sqldatasource


【解决方案1】:

嗯,没有硬性规则阻止任何人进行此类实施。

但是,在执行该实施之前,需要回答以下几个问题。

  1. 这种使用线程安全吗? (因为同一个调用很可能由多个消费应用程序进行。
  2. 是否会有分层区分(UI.Control 用于数据层)?
  3. 如果该控件在下一个框架版本中变得过时/受到限制怎么办?

【讨论】:

    【解决方案2】:

    鉴于替换此代码非常容易,同时消除了使用动态 SQL 查询来传递参数的诱惑,我认为问题应该是:保持代码原样有什么好处吗?

    例如:

    public static class Database
    {
        private static readonly Func<DbCommandBuilder, int, string> getParameterName = CreateDelegate("GetParameterName");
        private static readonly Func<DbCommandBuilder, int, string> getParameterPlaceholder = CreateDelegate("GetParameterPlaceholder");
    
        private static Func<DbCommandBuilder, int, string> CreateDelegate(string methodName)
        {
            MethodInfo method = typeof(DbCommandBuilder).GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic, Type.DefaultBinder, new Type[] { typeof(Int32) }, null);
            return (Func<DbCommandBuilder, int, string>)Delegate.CreateDelegate(typeof(Func<DbCommandBuilder, int, string>), method);
        }
    
        private static string GetDefaultProviderName()
        {
            ...
        }
    
        private static string GetDefaultConnectionString()
        {
            ...
        }
    
        public static DbProviderFactory GetProviderFactory()
        {
            string providerName = GetDefaultProviderName();
            return DbProviderFactories.GetFactory(providerName);
        }
    
        private static DbConnection GetDBConnection(DbProviderFactory factory)
        {
            DbConnection connection = factory.CreateConnection();
            connection.ConnectionString = GetDefaultConnectionString();
            return connection;
        }
    
        public static DbConnection GetDBConnection()
        {
            DbProviderFactory factory = GetProviderFactory();
            return GetDBConnection(factory);
        }
    
        private static void ProcessParameters(
            DbProviderFactory factory, 
            DbCommand command, 
            string query, 
            object[] queryParameters)
        {
            if (queryParameters == null && queryParameters.Length == 0)
            {
                command.CommandText = query;
            }
            else
            {
                IFormatProvider formatProvider = CultureInfo.InvariantCulture;
                DbCommandBuilder commandBuilder = factory.CreateCommandBuilder();
                StringBuilder queryText = new StringBuilder(query);
    
                for (int index = 0; index < queryParameters.Length; index++)
                {
                    string name = getParameterName(commandBuilder, index);
                    string placeholder = getParameterPlaceholder(commandBuilder, index);
                    string i = index.ToString("D", formatProvider);
    
                    command.Parameters.AddWithValue(name, queryParameters[index]);
                    queryText = queryText.Replace("{" + i + "}", placeholder);
                }
    
                command.CommandText = queryText.ToString();
            }
        }
    
        public static DataView GetDataView(string query, params object[] queryParameters)
        {
            DbProviderFactory factory = GetProviderFactory();
    
            using (DbConnection connection = GetDBConnection(factory))
            using (DbCommand command = connection.CreateCommand())
            {
                command.CommandType = CommandType.Text;
                ProcessParameters(factory, command, query, queryParameters);
    
                DbDataAdapter adapter = factory.CreateDataAdapter();
                adapter.SelectCommand = command;
    
                DataTable table = new DataTable();
                adapter.Fill(table);
                return table.DefaultView;
            }
        }
    }
    

    使用此版本,您现在可以简单安全地传入参数,而无需依赖自定义代码来尝试阻止 SQL 注入:

    DataView dv = Database.GetDataView(
       "select mycolumn from my table where id = {0} and name = {1}",
       1234, "Robert');DROP TABLE Students;--");
    

    编辑
    this answer 的帮助下更新以支持不同提供商的参数。

    【讨论】:

    • 仅供参考,SqlDataSource SelectCommand(和其他命令)不允许您在内部字符串之外的查询中放置分号。但我很欣赏你的代码。它可能正在做一些与 SqlDataSource 在内部所做的非常相似的事情。
    • @msm8bball:你确定吗?我刚刚使用查询"select * from sys.databases;select * from sys.objects;" 尝试了您的代码,它执行了这两个命令。
    • 我用 System.Data.OracleClient 进行了尝试,它给出了运行时异常。现在我想起来了,我不记得曾从 SQL Server 或任何其他数据提供程序中尝试过。也许那是 System.Data.OracleClient 特定的。
    • @msm8bball:我没用过OracleClient,而且AFAIK,它已经停产了。但是,我已经更新了我的答案,以说明不同提供者使用的不同参数名称语法。
    【解决方案3】:

    我看到的唯一问题是

    (1) 这就像重新发明轮子。 FW3.5 的企业库 v5 和 FW4.5 的 v6 具有数据访问组件。使用它。

    使用 EL,您可以拨打电话并在 Dataset 中加载 2、3、4 个表。用你的方法这是不可能的,一次只有一个。

    Enterprise library 是 Microsoft 提供的完整数据访问套件。它会处理所有的小细节,您所需要的只是调用您的数据。这是完整的数据访问层。如果你看得更深,EL 允许集成数据和缓存,以及其他东西。但是你不必使用你不需要的东西。如果您需要数据访问,则只能使用它。

    并且 (2) 通常,在参考中编写低级程序集和高级程序集并不是一个好主意。任何System.Web.... 都是 UI 和客户端相关的东西。在分层蛋糕设计中,这就像它的顶部,而数据访问位于底部。所有引用 [除了“common”] 都应该从底部到顶部,并且方向相反。

    看这张图:

    这是来自微软。你会看到“蛋糕”的层次。所有引用都在上升。你做了什么——你拿了 UI 相关的组件并在里面写了 Data Access。

    您可以将其称为基于意见的 - 但这种意见是软件开发中的标准做法和模式。您的问题也是基于意见的。因为您可以在单个文件、单个类中编写所有内容,并且它会起作用。如果需要,您可以在 Asp.net 应用程序中设置对 System.Windows.Forms 的引用。 从技术上讲,这是可能的,但这是非常糟糕的做法。

    您的应用程序现在的可重用性有限。如果您编写需要使用相同数据访问的 WPF 组件或服务怎么办。非得把System.Web都拖进去吗?

    【讨论】:

    • 企业库与我已经在做的相比有什么优势?你的第二点没有分量。您是在说“但是,它不应该那样使用!”不用说为什么那是个坏主意。如果您能提供具体示例说明为什么这是一个坏主意,我将不胜感激。
    • @msm8bball - 查看我的编辑。为什么?因为你没有关注点分离。阅读“固体”。您创建了 UI-DataAccess 组合。这在技术上是可行的(在这种情况下,因为 Microsoft 提供了用于“快速修复”的组件),但在良好的应用程序设计中这不会发生。
    • 关于您的编辑,我看不出这张照片有何不同。你没有解释为什么这是一个坏主意。你是说它不是 意味着 以这种方式使用而不解释 为什么 这是一个不好的做法。尽管 SqlDataSource 位于与 UI 相关的命名空间中,但它几乎与 UI 无关。事实上,我能看到他们首先将其设置为 Control 的唯一原因是微软可以说“ASP.NET 无需编写任何代码即可显示数据!”
    • 好的,这是另一个重要的“为什么”。因为您的应用程序现在的可重用性有限。如果您编写需要使用相同数据访问的 WPF 组件或服务怎么办。你非得把所有System.Web拖进去吗?正如我之前所说,这些控件中的许多实际上是在说“ASP.NET 无需编写任何代码即可显示数据!”。没有严肃的设计使用这些。人们编写他们的框架是因为此控件不处理应用程序中的横切关注点。
    • 就像我在帖子中所说的,我只是添加了对 System.Web 的引用。 Bam,它是可重复使用的。我已经在 ASP.NET、控制台和 Windows 窗体项目中成功使用了这个类。可重用性有限?
    猜你喜欢
    • 1970-01-01
    • 2012-08-22
    • 2016-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-24
    • 1970-01-01
    相关资源
    最近更新 更多