I am given an absolute URI that contains a query string. I'm looking to safely append a value to the query string, and change an existing parameter.
I would prefer not to tack on &foo=bar, or use regular expressions, URI escaping is tricky. Rather I want to use a built-in mechanism that I know will do this correctly and handle the escaping.
I've found a ton of answers that all use HttpUtility. However this being ASP.NET Core, there is no more System.Web assembly anymore, thus no more HttpUtility.
What is the appropriate way to do this in ASP.NET Core while targeting the core runtime?
If you are using ASP.NET Core 1 or 2, you can do this with Microsoft.AspNetCore.WebUtilities.QueryHelpers in the Microsoft.AspNetCore.WebUtilities package.
If you are using ASP.NET Core 3.0 or greater, WebUtilities is now part of the ASP.NET SDK and does not require a separate nuget package reference.
To parse it into a dictionary:
var uri = new Uri(context.RedirectUri);
var queryDictionary = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query);
Note that unlike ParseQueryString in System.Web, this returns a dictionary of type IDictionary<string, string[]> in ASP.NET Core 1.x, or IDictionary<string, StringValues> in ASP.NET Core 2.x or greater, so the value is a collection of strings. This is how the dictionary handles multiple query string parameters with the same name.
If you want to add a parameter on to the query string, you can use another method on QueryHelpers:
var parametersToAdd = new System.Collections.Generic.Dictionary<string, string> { { "resource", "foo" } };
var someUrl = "http://www.google.com";
var newUri = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(someUrl, parametersToAdd);
Using .net core 2.2 you can get the query string using
var request = HttpContext.Request;
var query = request.Query;
foreach (var item in query){
Debug.WriteLine(item)
}
You will get a collection of key:value pairs - like this
[0] {[companyName, ]}
[1] {[shop, ]}
[2] {[breath, ]}
[3] {[hand, ]}
[4] {[eye, ]}
[5] {[firstAid, ]}
[6] {[eyeCleaner, ]}
The easiest and most intuitive way to take an absolute URI and manipulate it's query string using ASP.NET Core packages only, can be done in a few easy steps:
Install Packages
PM> Install-Package Microsoft.AspNetCore.WebUtilities
PM> Install-Package Microsoft.AspNetCore.Http.Extensions
Important Classes
Just to point them out, here are the two important classes we'll be using: QueryHelpers, StringValues, QueryBuilder.
The Code
// Raw URI including query string with multiple parameters
var rawurl = "https://bencull.com/some/path?key1=val1&key2=val2&key2=valdouble&key3=";
// Parse URI, and grab everything except the query string.
var uri = new Uri(rawurl);
var baseUri = uri.GetComponents(UriComponents.Scheme | UriComponents.Host | UriComponents.Port | UriComponents.Path, UriFormat.UriEscaped);
// Grab just the query string part
var query = QueryHelpers.ParseQuery(uri.Query);
// Convert the StringValues into a list of KeyValue Pairs to make it easier to manipulate
var items = query.SelectMany(x => x.Value, (col, value) => new KeyValuePair<string, string>(col.Key, value)).ToList();
// At this point you can remove items if you want
items.RemoveAll(x => x.Key == "key3"); // Remove all values for key
items.RemoveAll(x => x.Key == "key2" && x.Value == "val2"); // Remove specific value for key
// Use the QueryBuilder to add in new items in a safe way (handles multiples and empty values)
var qb = new QueryBuilder(items);
qb.Add("nonce", "testingnonce");
qb.Add("payerId", "pyr_");
// Reconstruct the original URL with new query string
var fullUri = baseUri + qb.ToQueryString();
To keep up to date with any changes, you can check out my blog post about this here: http://benjii.me/2017/04/parse-modify-query-strings-asp-net-core/
HttpRequest has a Query property which exposes the parsed query string via the IReadableStringCollection interface:
/// <summary>
/// Gets the query value collection parsed from owin.RequestQueryString.
/// </summary>
/// <returns>The query value collection parsed from owin.RequestQueryString.</returns>
public abstract IReadableStringCollection Query { get; }
This discussion on GitHub points to it as well.
This function return Dictionary<string, string> and does not use Microsoft.xxx for compatibility
Accepts parameter encoding in both sides
Accepts duplicate keys (return last value)
var rawurl = "https://emp.com/some/path?key1.name=a%20line%20with%3D&key2=val2&key2=valdouble&key3=&key%204=44#book1";
var uri = new Uri(rawurl);
Dictionary<string, string> queryString = ParseQueryString(uri.Query);
// queryString return:
// key1.name, a line with=
// key2, valdouble
// key3,
// key 4, 44
public Dictionary<string, string> ParseQueryString(string requestQueryString)
{
Dictionary<string, string> rc = new Dictionary<string, string>();
string[] ar1 = requestQueryString.Split(new char[] { '&', '?' });
foreach (string row in ar1)
{
if (string.IsNullOrEmpty(row)) continue;
int index = row.IndexOf('=');
if (index < 0) continue;
rc[Uri.UnescapeDataString(row.Substring(0, index))] = Uri.UnescapeDataString(row.Substring(index + 1)); // use Unescape only parts
}
return rc;
}
It's important to note that in the time since the top answer has been flagged as correct that Microsoft.AspNetCore.WebUtilities has had a major version update (from 1.x.x to 2.x.x).
That said, if you're building against netcoreapp1.1 you will need to run the following, which installs the latest supported version 1.1.2:
Install-Package Microsoft.AspNetCore.WebUtilities -Version 1.1.2
I use this as extention method, works with any number of params:
public static string AddOrReplaceQueryParameter(this HttpContext c, params string[] nameValues)
{
if (nameValues.Length%2!=0)
{
throw new Exception("nameValues: has more parameters then values or more values then parameters");
}
var qps = new Dictionary<string, StringValues>();
for (int i = 0; i < nameValues.Length; i+=2)
{
qps.Add(nameValues[i], nameValues[i + 1]);
}
return c.AddOrReplaceQueryParameters(qps);
}
public static string AddOrReplaceQueryParameters(this HttpContext c, Dictionary<string,StringValues> pvs)
{
var request = c.Request;
UriBuilder uriBuilder = new UriBuilder
{
Scheme = request.Scheme,
Host = request.Host.Host,
Port = request.Host.Port ?? 0,
Path = request.Path.ToString(),
Query = request.QueryString.ToString()
};
var queryParams = QueryHelpers.ParseQuery(uriBuilder.Query);
foreach (var (p,v) in pvs)
{
queryParams.Remove(p);
queryParams.Add(p, v);
}
uriBuilder.Query = "";
var allQPs = queryParams.ToDictionary(k => k.Key, k => k.Value.ToString());
var url = QueryHelpers.AddQueryString(uriBuilder.ToString(),allQPs);
return url;
}
Next and prev links for example in a view:
var next = Context.Request.HttpContext.AddOrReplaceQueryParameter("page",Model.PageIndex+1+"");
var prev = Context.Request.HttpContext.AddOrReplaceQueryParameter("page",Model.PageIndex-1+"");
I'm not sure at what point it was added but as early as .NET Core 3.1 HttpUtility.ParseQueryString is available and built into the standard .NET Microsoft.NETCore.App framework. I was able to access it from a class library with no additional NuGet packages or special dll references required.
I've tested it with .NET Core 3.1, .NET 6, and .NET 7.
The benefit of this approach is that you do not have to reference any web libraries which will add bloat to your project if you are building a library that may be used outside of the context of ASP.NET.
... of course, if you are needing this within an ASP.NET Core app, the Microsoft.AspNetCore.WebUtilities class suggested by others is perfectly valid.
EDIT: #KTCO pointed out in his comment on the OP back in 2017 that this class was available in .NET Core 2.0 as well.
Related
I have a database in Entity Framework that has a set of DTOs created from it that are then consumed by Breeze from the client.
We use the DataAnnotations on the server to validate the data that comes in from Breeze and I want to be able to replicate these validators on the client. Since Breeze implements these validators already and apparently supports adding validators into the metadata I thought I'd give a go at extending the Breeze Server Project.
I am already aware that EDMXWriter only supports a small set of DataAnnotations.
Basically all my project does is add post-generation the required validators into the json that is sent by Breeze.
Here is Part of a 'Table' that has the DataAnnotation of StringLength (That Breeze does support) on the Title Property.
{
"name":"Table",
"customannotation:ClrType":"...",
"key":{
"propertyRef":{
"name":"Id"
}
},
"property":[
{
"name":"Title",
"type":"Edm.String",
"fixedLength":"false",
"unicode":"true",
"validators":[
{
"validatorName":"stringLength",
"maxLength":"Max",
"minLength":1
}
]
}
]
}
I've formatted the output generation to match the requirements set by the scheme on the breeze website: http://www.breezejs.com/documentation/metadata-schema
But Breeze is not interpreting these validators that I am adding to the Metadata.
I noticed that the schema provided by Breeze Server for EF has a different design to the Schema set on the web link above. Does BreezeJS not interpret validators of EF provided Metadata? And if that is the case is there an easy way to enable this or will I have to write that into the client too.
I was aware that the Breeze team did say that they were planning on implementing better EF DataAnnotation support however I've seen nothing come of that. Perhaps this is implemented already and I've missed something? One can only hope it will be that easy.
Regards,
Oliver Baker
There are two Metadata formats that breeze understands. The first, which is the default for an EDM (Entity Framework) based model, is a json serialized version of the EDMX CSDL. This is a MS format which cannot easily be extended, and only supports the limited number of data annotations listed above.
The other alternative is breeze's native metadata format. This format is typically used by any Non Entity Framework based breeze servers. This is also the format used when applying the MetadataStore.exportMetadata and MetadataStore.importMetadata method calls. If your server provides metadata in this format then you can include whatever validations you want. The best way to investigate this format is to simply export the metadata for your current application and take a look. The result is simply the stringified native metadata json.
One approach that several breeze developers have taken is use a prebuild process that roundtrips the CSDL formatted metadata from an EF server thru a breeze client to translate it into native format and then simply storing this result on the server ( in your case with some added validators) and simply returning this prestored metadata to the client in production during the Metadata call.
In addition, you can also extend the breeze metadata format: See:
http://www.breezejs.com/documentation/custom-metadata
We have a number of developers who use such extended metadata for a variety of purposes, including the addition of validation metadata.
It seems the EFContextProvider has very limited validation annotation support, basically just:
required - if !isNullable
maxLength - if maxLength is specified
The output listed at http://www.breezejs.com/documentation/metadata-schema is of the metadata object in the client library, once processed.
http://www.breezejs.com/documentation/validation shows how to manually edit this information, and notes the following:
Many of these validators correlate to .NET data annotations . In a future release, the Breeze.NET EFContextProviderwill be able to include these validations in the metadata automatically for you. For now, you'll have to add them to the properties on the client side as we show next.
So if you extend the EFContextProvider with additional metadata, you'll have to manually process this and add it to the validators objects in the property info in the metadata store.
One approach that several breeze developers have taken is use a prebuild process that roundtrips the CSDL formatted metadata from an EF server thru a breeze client to translate it into native format and then simply storing this result on the server
Here is my solution using jint, up on github. Obviously computationally expensive, so the method calling this has a [Conditional["DEBUG"]] attribute
public static class MedSimDtoMetadata
{
const string breezeJsPath = #"C:\Users\OEM\Documents\Visual Studio 2015\Projects\SimManager\SM.Web\Scripts\breeze.min.js";
public static string GetBreezeMetadata(bool pretty = false)
{
var engine = new Engine().Execute("var setInterval;var setTimeout = setInterval = function(){}"); //if using an engine like V8.NET, would not be required - not part of DOM spec
engine.Execute(File.ReadAllText(breezeJsPath));
engine.Execute("breeze.NamingConvention.camelCase.setAsDefault();" + //mirror here what you are doing in the client side code
"var edmxMetadataStore = new breeze.MetadataStore();" +
"edmxMetadataStore.importMetadata(" + MedSimDtoRepository.GetEdmxMetadata() + ");" +
"edmxMetadataStore.exportMetadata();");
var exportedMeta = JObject.Parse(engine.GetCompletionValue().AsString());
AddValidators(exportedMeta);
return exportedMeta.ToString(pretty ? Formatting.Indented : Formatting.None);
}
//http://stackoverflow.com/questions/26570638/how-to-add-extend-breeze-entity-types-with-metadata-pulled-from-property-attribu
static void AddValidators(JObject metadata)
{
Assembly thisAssembly = typeof(ParticipantDto).Assembly; //any type in the assembly containing the Breeze entities.
var attrValDict = GetValDictionary();
var unaccountedVals = new HashSet<string>();
foreach (var breezeEntityType in metadata["structuralTypes"])
{
string shortEntityName = breezeEntityType["shortName"].ToString();
string typeName = breezeEntityType["namespace"].ToString() + '.' + shortEntityName;
Type entityType = thisAssembly.GetType(typeName, true);
Type metaTypeFromAttr = ((MetadataTypeAttribute)entityType.GetCustomAttributes(typeof(MetadataTypeAttribute), false).Single()).MetadataClassType;
foreach (var breezePropertyInfo in breezeEntityType["dataProperties"])
{
string propName = breezePropertyInfo["name"].ToString();
propName = char.ToUpper(propName[0]) + propName.Substring(1); //IF client using breeze.NamingConvention.camelCase & server using PascalCase
var propInfo = metaTypeFromAttr.GetProperty(propName);
if (propInfo == null)
{
Debug.WriteLine("No metadata property attributes available for " + breezePropertyInfo["dataType"] + " "+ shortEntityName +'.' + propName);
continue;
}
var validators = breezePropertyInfo["validators"].Select(bp => bp.ToObject<Dictionary<string, object>>()).ToDictionary(key => (string)key["name"]);
//usingMetaProps purely on property name - could also use the DTO object itself
//if metadataType not found, or in reality search the entity framework entity
//for properties with the same name (that is certainly how I am mapping)
foreach (Attribute attr in propInfo.GetCustomAttributes())
{
Type t = attr.GetType();
if (t.Namespace == "System.ComponentModel.DataAnnotations.Schema") {
continue;
}
Func<Attribute, Dictionary<string,object>> getVal;
if (attrValDict.TryGetValue(t, out getVal))
{
var validatorsFromAttr = getVal(attr);
if (validatorsFromAttr != null)
{
string jsValidatorName = (string)validatorsFromAttr["name"];
if (jsValidatorName == "stringLength")
{
validators.Remove("maxLength");
}
Dictionary<string, object> existingVals;
if (validators.TryGetValue(jsValidatorName, out existingVals))
{
existingVals.AddOrOverwrite(validatorsFromAttr);
}
else
{
validators.Add(jsValidatorName, validatorsFromAttr);
}
}
}
else
{
unaccountedVals.Add(t.FullName);
}
}
breezePropertyInfo["validators"] = JToken.FromObject(validators.Values);
}
}
foreach (var u in unaccountedVals)
{
Debug.WriteLine("unaccounted attribute:" + u);
}
}
static Dictionary<Type, Func<Attribute, Dictionary<string, object>>> GetValDictionary()
{
var ignore = new Func<Attribute, Dictionary<string, object>>(x => null);
return new Dictionary<Type, Func<Attribute, Dictionary<string, object>>>
{
[typeof(RequiredAttribute)] = x => new Dictionary<string, object>
{
["name"] = "required",
["allowEmptyStrings"] = ((RequiredAttribute)x).AllowEmptyStrings
//["message"] = ((RequiredAttribute)x).ErrorMessage
},
[typeof(EmailAddressAttribute)] = x => new Dictionary<string, object>
{
["name"] = "emailAddress",
},
[typeof(PhoneAttribute)] = x => new Dictionary<string, object>
{
["name"] = "phone",
},
[typeof(RegularExpressionAttribute)] = x => new Dictionary<string, object>
{
["name"] = "regularExpression",
["expression"] = ((RegularExpressionAttribute)x).Pattern
},
[typeof(StringLengthAttribute)] = x => {
var sl = (StringLengthAttribute)x;
return GetStrLenDictionary(sl.MaximumLength, sl.MinimumLength);
},
[typeof(MaxLengthAttribute)] = x => GetStrLenDictionary(((MaxLengthAttribute)x).Length),
[typeof(UrlAttribute)] = x => new Dictionary<string, object>
{
["name"] = "url",
},
[typeof(CreditCardAttribute)] = x=> new Dictionary<string, object>
{
["name"] = "creditCard",
},
[typeof(FixedLengthAttribute)] = x => //note this is one of my attributes to force fixed length
{
var len = ((FixedLengthAttribute)x).Length;
return GetStrLenDictionary(len, len);
},
[typeof(RangeAttribute)] = x => {
var ra = (RangeAttribute)x;
return new Dictionary<string, object>
{
["name"] = "range",
["min"] = ra.Minimum,
["max"] = ra.Maximum
};
},
[typeof(KeyAttribute)] = ignore
};
}
static Dictionary<string,object> GetStrLenDictionary(int maxLength, int minLength = 0)
{
if (minLength == 0)
{
return new Dictionary<string, object>
{
["name"] = "maxLength",
["maxLength"] = maxLength
};
}
return new Dictionary<string, object>
{
["name"] = "stringLength",
["minLength"] = minLength,
["maxLength"] = maxLength
};
}
static void AddOrOverwrite<K,V>(this Dictionary<K,V> oldValues, Dictionary<K,V> newValues)
{
foreach (KeyValuePair<K,V> kv in newValues)
{
if (oldValues.ContainsKey(kv.Key))
{
oldValues[kv.Key] = kv.Value;
}
else
{
oldValues.Add(kv.Key, kv.Value);
}
}
}
}
I use Nest client to use ElasticSearch .I want to search in ElasticSearch :
SearchRequest countRequest = new SearchRequest
{
//Somthing
};
client.Search<Post>(countRequest);
On other hand :
client.Search<Post>(s=>s.Index("IndexName").Query(...))
How i can set index name by SearchRequest class search ?
This is for those using newer versions of NEST. In 2.0.1, I am unable to find the Indices property in SearchRequest. However, you can pass them in through the constructor:
var request = new SearchRequest<Post>("IndexName", "TypeName");
I map the index and type on the ConnectionSettings like so.
ConnectionSettings settings = new ConnectionSettings("url");
settings.MapDefaultTypeIndices(t => t.Add(typeof(Post), "IndexName"));
settings.MapDefaultTypeNames(t => t.Add(typeof(Post), "TypeName"));
Other ways to tell NEST the index and type:
client.Search<Post>(s => s.Index("IndexName").Type("TypeName").From(0));
or apply the ElasticsearchTypeAttribute on the type.
[ElasticsearchType(Name = "TypeName")]
public class Post{ }
SearchRequest contains an Indices property, so that you can specify multiple indices to search across. In your case, you could just pass the single index like so:
var request = new SearchRequest
{
Indices = new IndexNameMarker[] { "IndexName" }
};
Another option would be to map your Post type to the index it belongs to, and use the typed SearchRequest<T> to let NEST infer the index name.
I was trying to solve a bit different task with ES v5 (json request was pushed from the file) but also had the same problem with setting the indexName. So, my solution was to add index querystring parameter. Using this in integration tests:
public static class ElasticSearchClientHelper
{
public static ISearchResponse<T> SearchByJson<T>(this IElasticClient client, string json, string indexName, Dictionary<string, object> queryStringParams = null) where T : class
{
var qs = new Dictionary<string, object>()
{
{"index", indexName}
};
queryStringParams?.ForEach(pair => qs.Add(pair.Key, pair.Value));
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
{
var searchRequest = client.Serializer.Deserialize<SearchRequest>(stream);
((IRequestParameters)((IRequest<SearchRequestParameters>)searchRequest).RequestParameters).QueryString = qs;
return client.Search<T>(searchRequest);
}
}
}
We have a template URL like:
http://api.example.com/sale?auth_user=xxxxx&auth_pass=xxxxx&networkid={networkid}&category=b2c&country=IT&pageid={pageid}&programid=133&saleid=1&m={master}&optinfo={optinfo}&publisher={publisher}&msisdn={userId}
and I have values for these constant tokens. How can replace all these tokens in C#?
A simple approach is to use a foreach and a Dictionary with a String.Replace:
var values = new Dictionary<string, string> {
{ "{networkid}", "WHEEE!!" }
// etc.
};
var url = "http://api.example.com/sale?auth_user=xxxxx&auth_pass=xxxxx&networkid={networkid}&category=b2c&country=IT&pageid={pageid}&programid=133&saleid=1&m={master}&optinfo={optinfo}&publisher={publisher}&msisdn={userId}";
foreach(var key in values.Keys){
url = url.Replace(key,values[key]);
}
There is no standard way to "replace with dictionary values" in .NET. While there are a number of template engines, it's not very hard to write a small solution for such an operation. Here is an example which runs in LINQPad and utilizes a Regular Expression with a Match Evaluator.
As the result is a URL,
it is the callers responsibility to make sure all the supplied values are correctly encoded. I recommend using Uri.EscapeDataString as appropriate .. but make sure to not double-encode, if it is processed elsewhere.
Additionally, the rules of what to do when no replacement is found should be tailored to need. If not-found replacements should be eliminated entirely along with the query string key, the following can expand the regular expression to #"\w+=({\w+})" to also capture the parameter key in this specific template situation.
string ReplaceUsingDictionary (string src, IDictionary<string, object> replacements) {
return Regex.Replace(src, #"{(\w+)}", (m) => {
object replacement;
var key = m.Groups[1].Value;
if (replacements.TryGetValue(key, out replacement)) {
return Convert.ToString(replacement);
} else {
return m.Groups[0].Value;
}
});
}
void Main()
{
var replacements = new Dictionary<string, object> {
{ "networkid", "WHEEE!!" }
// etc.
};
var src = "http://api.example.com/sale?auth_user=xxxxx&auth_pass=xxxxx&networkid={networkid}&category=b2c&country=IT&pageid={pageid}&programid=133&saleid=1&m={master}&optinfo={optinfo}&publisher={publisher}&msisdn={userId}";
var res = ReplaceUsingDictionary(src, replacements);
// -> "http://api.example.com/sale?..&networkid=WHEEE!!&..&pageid={pageid}&..
res.Dump();
}
More advanced techniques, like reflection and transforms, are possible - but those should be left for the real template engines.
I am guessing you are trying to replace parameters in url with your values. This can be done using C# HttpUtility.ParseQueryString
Get the CurrentURL from
var _myUrl = System.Web.HttpUtility.ParseQueryString(Request.RawUrl);
Read Parameter from your Query string
string value1 = _myUrl ["networkid"];
Write a value into the QueryString object.
_myUrl ["networkid"] = "Your Value";
and then finally turn it back into URL
var _yourURIBuilder= new UriBuilder(_myUrl );
_myUrl = _yourURIBuilder.ToString();
You can use this alos using LinQ
Dictionary<string, string> myVal = new Dictionary<string, string>();
myVal.Add("networkid", "1");
myVal.Add("pageid", "2");
myVal.Add("master", "3");
myVal.Add("optinfo", "4");
myVal.Add("publisher", "5");
myVal.Add("userId", "6");
string url = #"http://api.example.com/sale?auth_user=xxxxx&auth_pass=xxxxx&networkid={networkid}&category=b2c&country=IT&pageid={pageid}&programid=133&saleid=1&m={master}&optinfo={optinfo}&publisher={publisher}&msisdn={userId}";
myVal.Select(a => url = url.Replace(string.Concat("{", a.Key, "}"), a.Value)).ToList();
this line can do your required functionlity
myVal.Select(a => url = url.Replace(string.Concat("{", a.Key, "}"), a.Value)).ToList();
There is a Nuget called StringTokenFormatter that does this well
https://www.nuget.org/packages/StringTokenFormatter/
Regex.Replace makes a single pass over a template string, offering you an opportunity to replace matched expressions. Use it by creating an regular expression that matches any token. Then look up replacement values for the tokens in a dictionary.
static string ReplaceTokens(string template, Dictionary<string, string> replacements) =>
Regex.Replace(template, #"{(\w+)}",
match => replacements.TryGetValue(match.Groups[1].Value, out string replacement) ? replacement : match.Value);
The algorithm completes in time linear with the size of the template string and the replacement strings, so O(t + r). Beware of algorithms that make multiple passes. They run slowly in time O(t * r) and will give incorrect results if one of the replacement values contains a token for later replacement. This unit test shows the pitfall:
public void TestReplaceTokens() {
var replacements = new Dictionary<string, string> {
["Movement"] = "drive {DontReplace}",
["DontReplace"] = "Should not appear"
};
string withReplacements = ReplaceTokens("I always {Movement} in {Direction}.", replacements);
Assert.AreEqual("I always drive {DontReplace} in {Direction}.", withReplacements);
}
With the following code:
string q = "userID=16555&gameID=60&score=4542.122&time=343114";
What would be the easiest way to parse the values, preferably without writing my own parser? I'm looking for something with the same functionality as Request.querystring["gameID"].
Pretty easy... Use the HttpUtility.ParseQueryString method.
Untested, but this should work:
var qs = "userID=16555&gameID=60&score=4542.122&time=343114";
var parsed = HttpUtility.ParseQueryString(qs);
var userId = parsed["userID"];
// ^^^^^^ Should be "16555". Note this will be a string of course.
You can do it with linq like this.
string query = "id=3123123&userId=44423&format=json";
Dictionary<string,string> dicQueryString =
query.Split('&')
.ToDictionary(c => c.Split('=')[0],
c => Uri.UnescapeDataString(c.Split('=')[1]));
string userId = dicQueryString["userID"];
Edit
If you can use HttpUtility.ParseQueryString then it will be a lot more straight forward and it wont be case-sensitive as in case of LinQ.
As has been mentioned in each of the previous answers, if you are in a context where you can add a dependency to the System.Web library, using HttpUtility.ParseQueryString makes sense. (For reference, the relevant source can be found in the Microsoft Reference Source). However, if this is not possible, I would like to propose the following modification to Adil's answer which accounts for many of the concerns addressed in the comments (such as case sensitivity and duplicate keys):
var q = "userID=16555&gameID=60&score=4542.122&time=343114";
var parsed = q.TrimStart('?')
.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)
.Select(k => k.Split('='))
.Where(k => k.Length == 2)
.ToLookup(a => a[0], a => Uri.UnescapeDataString(a[1])
, StringComparer.OrdinalIgnoreCase);
var userId = parsed["userID"].FirstOrDefault();
var time = parsed["TIME"].Select(v => (int?)int.Parse(v)).FirstOrDefault();
If you want to avoid the dependency on System.Web that is required to use HttpUtility.ParseQueryString, you could use the Uri extension method ParseQueryString found in System.Net.Http.
Note that you have to convert the response body to a valid Uri so that ParseQueryString works.
Please also note in the MSDN document, this method is an extension method for the Uri class, so you need reference the assembly System.Net.Http.Formatting (in System.Net.Http.Formatting.dll). I tried installed it by the nuget package with the name "System.Net.Http.Formatting", and it works fine.
string body = "value1=randomvalue1&value2=randomValue2";
// "http://localhost/query?" is added to the string "body" in order to create a valid Uri.
string urlBody = "http://localhost/query?" + body;
NameValueCollection coll = new Uri(urlBody).ParseQueryString();
How is this
using System.Text.RegularExpressions;
// query example
// "name1=value1&name2=value2&name3=value3"
// "?name1=value1&name2=value2&name3=value3"
private Dictionary<string, string> ParseQuery(string query)
{
var dic = new Dictionary<string, string>();
var reg = new Regex("(?:[?&]|^)([^&]+)=([^&]*)");
var matches = reg.Matches(query);
foreach (Match match in matches) {
dic[match.Groups[1].Value] = Uri.UnescapeDataString(match.Groups[2].Value);
}
return dic;
}
System.Net.Http ParseQueryString extension method worked for me. I'm using OData query options and trying to parse out some custom parameters.
options.Request.RequestUri.ParseQueryString();
Seems to give me what I need.
HttpUtility.ParseQueryString will work as long as you are in a web app or don't mind including a dependency on System.Web. Another way to do this is:
// NameValueCollection nameValueCollection = HttpUtility.ParseQueryString(queryString);
NameValueCollection nameValueCollection = new NameValueCollection();
string[] querySegments = queryString.Split('&');
foreach(string segment in querySegments)
{
string[] parts = segment.Split('=');
if (parts.Length > 0)
{
string key = parts[0].Trim(new char[] { '?', ' ' });
string val = parts[1].Trim();
nameValueCollection.Add(key, val);
}
}
For .NET Core there is Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery
var queryString = QueryHelpers.ParseQuery("?param1=value");
var queryParamValue = queryString["param1"];
Code snippet modified from trackjs.com:
What I have
I have templates that are stored in a database, and JSON data that gets converted into a dictionary in C#.
Example:
Template: "Hi {FirstName}"
Data: "{FirstName: 'Jack'}"
This works easily with one level of data by using a regular expression to pull out anything within {} in the template.
What I want
I would like to be able to go deeper in the JSON than the first layer.
Example:
Template: "Hi {Name: {First}}"
Data: "{Name: {First: 'Jack', Last: 'Smith'}}"
What approach should I be taking? (and some guidance on where to start with your pick)
A regular expression
Not use JSON in the template (in favor of xslt or something similar)
Something else
I'd also like to be able to loop through data in the template, but I have no idea at all where to start with that one!
Thanks heaps
You are in luck! SmartFormat does exactly as you describe. It is a lightweight, open-source string formatting utility.
It supports named placeholders:
var template = " {Name:{Last}, {First}} ";
var data = new { Name = new { First="Dwight", Last="Schrute" } };
var result = Smart.Format(template, data);
// Outputs: " Schrute, Dwight " SURPRISE!
It also supports list formatting:
var template = " {People:{}|, |, and} ";
var data = new { People = new[]{ "Dwight", "Michael", "Jim", "Pam" } };
var result = Smart.Format(template, data);
// Outputs: " Dwight, Michael, Jim, and Pam "
You can check out the unit tests for Named Placeholders and List Formatter to see plenty more examples!
It even has several forms of error-handling (ignore errors, output errors, throw errors).
Note: the named placeholder feature uses reflection and/or dictionary lookups, so you can deserialize the JSON into C# objects or nested Dictionaries, and it will work great!
Here is how I would do it:
Change your template to this format Hi {Name.First}
Now create a JavaScriptSerializer to convert JSON in Dictionary<string, object>
JavaScriptSerializer jss = new JavaScriptSerializer();
dynamic d = jss.Deserialize(data, typeof(object));
Now the variable d has the values of your JSON in a dictionary.
Having that you can run your template against a regex to replace {X.Y.Z.N} with the keys of the dictionary, recursively.
Full Example:
public void Test()
{
// Your template is simpler
string template = "Hi {Name.First}";
// some JSON
string data = #"{""Name"":{""First"":""Jack"",""Last"":""Smith""}}";
JavaScriptSerializer jss = new JavaScriptSerializer();
// now `d` contains all the values you need, in a dictionary
dynamic d = jss.Deserialize(data, typeof(object));
// running your template against a regex to
// extract the tokens that need to be replaced
var result = Regex.Replace(template, #"{?{([^}]+)}?}", (m) =>
{
// Skip escape values (ex: {{escaped value}} )
if (m.Value.StartsWith("{{"))
return m.Value;
// split the token by `.` to run against the dictionary
var pieces = m.Groups[1].Value.Split('.');
dynamic value = d;
// go after all the pieces, recursively going inside
// ex: "Name.First"
// Step 1 (value = value["Name"])
// value = new Dictionary<string, object>
// {
// { "First": "Jack" }, { "Last": "Smith" }
// };
// Step 2 (value = value["First"])
// value = "Jack"
foreach (var piece in pieces)
{
value = value[piece]; // go inside each time
}
return value;
});
}
I didn't handle exceptions (e.g. the value couldn't be found), you can handle this case and return the matched value if it wasn't found. m.Value for the raw value or m.Groups[1].Value for the string between {}.
Have you thought of using Javascript as your scripting language? I had great success with Jint, although the startup cost is high. Another option is Jurassic, which I haven't used myself.
If you happen to have a Web Application, using Razor maybe an idea, see here.
Using Regex or any sort of string parsing can certainly work for trivial things, but can get painful when you want logic or even just basic hierarchies. If you deserialize your JSON into nested Dictionaries, you can build a parser relatively easily:
// Untested and error prone, just to illustrate the concept
var parts = "parentObj.childObj.property".split('.');
Dictionary<object,object> current = yourDeserializedObject;
foreach(var key in parts.Take(parts.Length-1)){
current = current[key];
}
var value = current[parts.Last()];
Just whatever you do, don't do XSLT. Really, if XSLT is the answer then the question must have been really desperate :)
Why not us nvelocity or something?