Hello,
I'm seeing this error when running the code on SQL 2016. On SQL 2017 it works fine.
Argument 2 of the "JSON_VALUE or JSON_QUERY" must be a string literal.
I updated the 2nd argument to a string literal and it fixed the error and works fine.
But am I open to SQL Injection now due to this? Can someone explain?
Working Code:
public async Task<IList<FileSystemItemJsonDTO>> GetFileSystemItems(int moduleId, IDictionary<string, string> metadata)
{
var sqlParams = new List<SqlParameter>();
StringBuilder sb = new StringBuilder();
// start the initial select query...
sb.Append("SELECT * FROM dbo.FileSystemItems WHERE ");
int counter = 0;
foreach (var item in metadata)
{
// only add an AND if we are NOT the first record...
if (counter != 0)
{
sb.Append(" AND ");
}
// setup our json path and value items...
string valueParam = string.Format(CultureInfo.CurrentCulture, "jsonPathValue{0}", counter);
// 2nd item for JSON_VALUE has to be string literal for SQL server 2016
sb.AppendFormat(CultureInfo.CurrentCulture, "JSON_VALUE(FileMetadata, '$.{0}') = @{1}", item.Key, valueParam);
// add in our parameters to assist with sql injection
sqlParams.Add(new SqlParameter(valueParam, string.Format(CultureInfo.CurrentCulture, "{0}", item.Value)));
counter++;
}
return await BIContext.FileSystemItems
.Where(x => x.ModuleId == moduleId)
.FromSql(sb.ToString(), sqlParams.ToArray())
.Select(s => new FileSystemItemJsonDTO
{
FileId = s.FileId,
FileName = s.FileName,
FileType = s.FileType,
LastWriteTime = s.LastWriteTime,
FileSystemItemDataId = s.FileSystemItemDataId,
ModuleId = moduleId,
FileMetadata = s.FileMetadata,
FileSize = s.FileSize
})
.ToListAsync().ConfigureAwait(false);
}Failing Code:
public async Task<IList<FileSystemItemJsonDTO>> GetFileSystemItems(int moduleId, IDictionary<string, string> metadata)
{
var sqlParams = new List<SqlParameter>();
StringBuilder sb = new StringBuilder();
// start the initial select query...
sb.Append("SELECT * FROM dbo.FileSystemItems WHERE ");
int counter = 0;
foreach (var item in metadata)
{
// only add an AND if we are NOT the first record...
if (counter != 0)
{
sb.Append(" AND ");
}
// setup our json path and value items...
string pathParam = string.Format(CultureInfo.CurrentCulture, "jsonPathParam{0}", counter);
string valueParam = string.Format(CultureInfo.CurrentCulture, "jsonPathValue{0}", counter);
sb.AppendFormat(CultureInfo.CurrentCulture, "JSON_VALUE(FileMetadata, @{0}) = @{1}", pathParam, valueParam);
// add in our parameters to assist with sql injection
sqlParams.Add(new SqlParameter(pathParam, string.Format(CultureInfo.CurrentCulture, "$.{0}", item.Key)));
sqlParams.Add(new SqlParameter(valueParam, item.Value));
counter++;
}
return await BIContext.FileSystemItems
.Where(x => x.ModuleId == moduleId)
.FromSql(sb.ToString(), sqlParams.ToArray())
.Select(s => new FileSystemItemJsonDTO
{
FileId = s.FileId,
FileName = s.FileName,
FileType = s.FileType,
LastWriteTime = s.LastWriteTime,
FileSystemItemDataId = s.FileSystemItemDataId,
ModuleId = moduleId,
FileMetadata = s.FileMetadata,
FileSize = s.FileSize
})
.ToListAsync().ConfigureAwait(false);
}Notice the difference on this line:
// 2nd item for JSON_VALUE has to be string literal for SQL server 2016
sb.AppendFormat(CultureInfo.CurrentCulture, "JSON_VALUE(FileMetadata, '$.{0}') = @{1}", item.Key, valueParam);