I have this url pattern
http://dev.virtualearth.net/REST/v1/Locations?
addressLine={0}&
adminDistrict={1}&
locality={2}&
countryRegion={3}&
postalCode={4}&
userLocation={5}&
inclnb=1&
key={6}
Let us say that locality and userLocation have no values
http://dev.virtualearth.net/REST/v1/Locations?
addressLine=Main&
adminDistrict=WA&
locality=&
countryRegion=US&
postalCode=98001&
userLocation=&
inclnb=1&
key=BingKey
Then I want to remove all parameters that is equal to "&"
Like for example: 'locality=&' and 'userLocation=&'
And should be look like this:
http://dev.virtualearth.net/REST/v1/Locations?
addressLine=Main&
adminDistrict=WA&
countryRegion=US&
postalCode=98001&
inclnb=1&
key=BingKey
Final Output:
http://dev.virtualearth.net/REST/v1/Locations?addressLine=Main&adminDistrict=WA&countryRegion=US&postalCode=98001&inclnb=1&key=BingKey
Why do you specificly want to use regular expressions? There are some classes in C# specificly build for building and handling URIs. I suggest you look at HttpUtility.ParseQueryString() or Uri.TryCreate.
You would then parse the query string, loop through the variables that have only a key and no value, and reconstruct a new uri without them. It will be much easier to read and maintain than a regular expression.
Edit: I quickly decided to see how this could be done:
string originalUri = "http://www.example.org/etc?query=string&query2=&query3=";
// Create the URI builder object which will give us access to the query string.
var uri = new UriBuilder(originalUri);
// Parse the querystring into parts
var query = System.Web.HttpUtility.ParseQueryString(uri.Query);
// Loop through the parts to select only the ones where the value is not null or empty
var resultQuery = query.AllKeys
.Where(k => !string.IsNullOrEmpty(query[k]))
.Select(k => string.Format("{0}={1}", k, query[k]));
// Set the querystring part to the parsed version with blank values removed
uri.Query = string.Join("&",resultQuery);
// Done, uri now contains "http://www.example.org/etc?query=string"
#"[\w]+=\&" should get you what you are looking for, but wouldn't it be easier to simply not add the parameter to the url string if the corresponding value is empty?
Related
i'm building an url using flurl.
This is an example of what i am doing:
var res = baseUrl.AppendPathSegment(a).SetQueryParam(b);
I would like to make flurl skip adding "a" or "b" when they are string.empty. Is that possible?
At the moment i see that flurl is adding "a" and "b" to the url even if they are empty.
Thank you
Let's test a few scenarios:
"http://example.com/".AppendPathSegment("").AppendPathSegment("")
Result: http://example.com/, which (if I understand you correctly) is exactly what you want. But, AppendPathSegment will throw an exception if you pass null, so I would suggest this in your case:
baseUrl.AppandPathSegment(a ?? "")
Next up:
"http://example.com/".AppendPathSegment("").SetQueryParam("x", null)
Also leaves you with http://example.com/. But an empty string value (instead of null) will append ?x=, so you may need to replace any empty string with null in this case.
However, it looks like you're using the less-common overload of AppendQueryParam that takes a single argument.
"http://example.com/".SetQueryParam("")
"http://example.com/".SetQueryParam(null)
In both cases the result is http://example.com/?. The server shouldn't behave differently in this case than it would without the ?, but if you're finding that's not true in your case, then Flurl doesn't have a built-in way to deal with it. You'll need to either avoid it with an if statement or use TrimEnd('?') on the string result.
If you don't want to use the path segment or query param because the parameter a or b is empty or null use an if statement:
var res = baseUrl;
if(!string.IsNullOrWhiteSpace(a))
{
res = res.AppendPathSegment(a);
}
if(!string.IsNullOrWhiteSpace(b))
{
res = res.SetQueryParam(b);
}
I am trying to write a Linq query to find items with the string property Tags containing at least one of the words in a comma separated string, queryTags.
The property Tags is also a comma separated string, and a value could be "toy,ball,red,plastic".
The search-string queryTags could be for instance "red,ball". In this case, the item should be selected because both "red" and "ball" matches.
This is what I have so far:
Model
public class WebContentsImage
{
public int Id { get; set; }
public string Tags { get; set; } // E.g. "toy,ball,red,plastic"
// some more properties
}
Query
List<WebContentsImage> Images = await db.WebContentsImages
.Where(t => t.Tags.Any(queryTags.Contains))
.ToListAsync()
.ConfigureAwait(false);
I don't really understand what the query above should be doing. I have just copied it from this answer, hoping it aligned with what I am trying to do. Anyway, I get this runtime exception from it:
NotSupportedException: Could not parse expression 't.Tags.Any(Convert(__CreateDelegate_0, Func`2))': The given arguments did not match the expected arguments: Object of type 'System.Linq.Expressions.UnaryExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.
Previously, I had this:
List<WebContentsImage> Images = await db.WebContentsImages
.Where(t => t.Tags.Contains(queryTags.Split(",")))
.ToListAsync()
.ConfigureAwait(false);
... which has this compile time error:
Argument 1: cannot convert from string[] to char
And this (which is just plain wrong):
List<WebContentsImage> Images = await db.WebContentsImages
.Where(t => t.Tags.Contains(queryTags, StringComparison.CurrentCultureIgnoreCase))
.ToListAsync()
.ConfigureAwait(false);
You’re using a db it seems, so you need to remain mindful of what can and what cannot be converted into an SQL
You might find better success with something like:
var x = db.WebContentsImages;
foreach(string tag in queryTags.Split(','))
x = x.Where(t => (',' + t.Tags + ',').Contains(',' + tag + ',', StringComparison.CurrentCultureIgnoreCase));
var list = x.ToListAsync();
The repeated where will act cumulatively and hopefully generate a set of sql like:
SELECT * FROM table
WHERE ','+tags+',' LIKE '%,red,%' AND
','+tags+',' LIKE '%,ball,%'
I should point out though that this is a fairly horrific way to store the data in the db and ask it to return things based on string LIKE.. tags should really have their own table and then a middleman table maps what Things have which Tags
You’re becoming confused with the current structure because string has a Contains method that returns true if a substring exists within this string, and Linq extends ienumerable collections to also have a different Contains method that tells whether a collection Contains a particular element. You’re mixing the two and asking string Contains to report on whether the string Contains an array etc. You need to be using the LINQ Contains all the time, which means splitting your Tags on comma and then asking if the resulting array of strings contains all of the array of strings resulting from splitting the queryTags on comma too
The problem is that while that can happen on the client I doubt your ORM will be able to carry out a split on the server side which means it will have to drag the entire db table down to the client. This is why I went the other way and converted everything to use String Contains in the hopes that it will become a bunch of LIKE AND on the server..
It would be better not to store the data this way
If Tags and queryTags were some kind of IEnumerable of string, you’d stand a better chance of saying
products.Where(
product => queryTags.All(queryTag => product.Tags.Contains(queryTag)
)
In English this is “for all products, filter to only products where all of the queryTags are present in the product.Tags list
You could manipulate your current data thus; make queryTags a string result from splitting the query string on comma, and rename your Tags csv string as TagsCsv and make a new property called Tags that returns TagsCsv.Split(',') but I wholly expect that it would have to be executed on the client, not in the db
var queryTags = “red,ball”.Split(',');
//horrific, for illustration purposes only
class Product{
String TagsCsv = “red,plastic,round,ball”;
String[] Tags { get { return TagsCsv.Split(',') } }
}
this should do it
List<WebContentsImage> Images = await db.WebContentsImages
.Where(i => i.Tag.Split(",").Any(t => queryTags.Split(",").Contains(t)));
.ToListAsync()
.ConfigureAwait(false);
you were nearly there
I am using the latest version of Mongo C# driver which uses a lot of Async and builder pattern. Which is nice. I am trying to convert SQL where clauses into Mongo FilterDefinition object.
Any idea how to handle "contains"?
like:
where x contains 'ABC'
In order to achieve that in V2 API, use the `Filter.Regex':
var collection = db.GetCollection<BsonDocument>("collection");
var filter = Builders<BsonDocument>.Filter.Regex("fieldName", new BsonRegularExpression(".*fieldValue.*"));
var data = await (await coll.FindAsync<BsonDocument>(filter).ConfigureAwait(false)).ToListAsync();
//continue process data
If x is a string, you could do so with a simple regex. For the 2.0 driver, you can manually create the FilterDefinition:
FilterDefinition<BsonDocument> filter = "{ x : { $regex : /ABC/ } }";
Or build the filter use the Builder:
var builder = Builders<BsonDocument>.Filter;
var filter = builder.Matches("x", "ABC");
Then you can use the filter in your query:
using (var cursor = await collection.Find(filter).ToCursorAsync())
{
// ...
}
I was able to get this working using Filter.AnyIn like so
var filter = Builders<BsonDocument>.Filter.AnyIn("x", new List<string> { "ABC" });
This works if you're looking for multiple values too, just add them to the list.
First, I highly recommend taking MongoDB University's .NET course (from Mongo itself). It's really thorough, and covers your question (and more) in depth.
Second, I assume that x is an array in your example.
MongoDB correctly handles polymorphism with arrays. If you have a class Post with an array of Tags, you can filter where Tag = ABC.
If you're using the C# linq methods, that looks like .Find(p => p.Tags == "ABC"). If you're using BsonDocument, that looks like new BsonDocument().Add("Tags", "ABC").
I have another way which I don't love but it works. The answer that is marked correct is half wrong (Matches is a method of Builders). In this example the / act like a % in a sql query LIKE statement. I'm still looking for a better way and will update if I find one that is more Equals filter below.
List<yourobject> someList = await collection.Find("{ x: /Test/ }").ToListAsync();
var filter = Builders<yourobject>.Filter.Eq("x", "ABC");
List<yourobject> someList = await collection.Find(filter).ToListAsync();
If you want to just search input text you need to replace regex special characters.
Regex.Escape will ensure that these characters are processed literally rather than as metacharacters. Otherwise input text can be used to query regex patterns which is probably not what is required.
var text = "ABC";
var filter = Builders<BsonDocument>.Filter.Regex("x", BsonRegularExpression.Create(Regex.Escape(text)));
If you need case insensitive check. Then you can pass case insensitive regex to BsonRegularExpression.Create:
var text = "ABC";
var escapeText = Regex.Escape(text);
var regex = new Regex(escapeText, RegexOptions.IgnoreCase);
var filter = Builders<BsonDocument>.Filter.Regex("x", BsonRegularExpression.Create(regex));
I am using following code:
var names = ConfigurationManager.AppSettings.AllKeys.Where(k => k.StartsWith("name"));
and i get keys like: name1, name2, name16, name18.
Now i want to create another array which will remove name and just keep 1,2,16,18. Is there any easy way to do this in above code itself? Or do it seperatly?
You can directly
ConfigurationManager.AppSettings.AllKeys.Where(k => k.StartsWith("name")).Select(a => a.Replace("name",""));
I think your code is good enough. Just a little bit of performance improve by using substring as it's straightforward operation to remove prefix:
var prefix = "name"; // could be a parameter or used as const; just for example
var nums = ConfigurationManager.AppSettings.AllKeys.Where(s => s.StartsWith(prefix)).Select(s => s.Substring(prefix.Length)).ToArray();
Try this:
var names = ConfigurationManager.AppSettings.AllKeys.Where(k => k.StartsWith("name")).Select(p => p.Replace("name", ""));
Use:
var namesWithoutPrefix = names.Select(n => n.Substring(4));
Doing Replace instead of Substring might replace several substrings in one name (and is slower even if it doesn't do so).
I would not recommend relying on the position of the numeric value or the length of the string or the fact that the text reads 'name' at all. Instead you can use a simple regular expression to extract it consistently:
Regex regex = new Regex(#"[0-9]+");
var numbers = ConfigurationManager.AppSettings
.AllKeys.Select(p => regex.Match(p).Value).ToArray();
In my base page I need to remove an item from the query string and redirect. I can't use
Request.QueryString.Remove("foo")
because the collection is read-only. Is there any way to get the query string (except for that one item) without iterating through the collection and re-building it?
You can avoid touching the original query string by working on a copy of it instead. You can then redirect the page to the a url containing your modified query string like so:
var nvc = new NameValueCollection();
nvc.Add(HttpUtility.ParseQueryString(Request.Url.Query));
nvc.Remove("foo");
string url = Request.Url.AbsolutePath;
for (int i = 0; i < nvc.Count; i++)
url += string.Format("{0}{1}={2}", (i == 0 ? "?" : "&"), nvc.Keys[i], nvc[i]);
Response.Redirect(url);
Update:
Turns out we can simplify the code like so:
var nvc = HttpUtility.ParseQueryString(Request.Url.Query);
nvc.Remove("foo");
string url = Request.Url.AbsolutePath + "?" + nvc.ToString();
Response.Redirect(url);
You'd have to reconstruct the url and then redirect. Something like this:
string url = Request.RawUrl;
NameValueCollection params = Request.QueryString;
for (int i=0; i<params.Count; i++)
{
if (params[i].GetKey(i).ToLower() == "foo")
{
url += string.Concat((i==0 ? "?" : "&"), params[i].GetKey(i), "=", params.Get(i));
}
}
Response.Redirect(url);
Anyway, I didn't test that or anything, but it should work (or at least get you in thye right direction)
Response.Redirect(String.Format("nextpage.aspx?{0}", Request.QueryString.ToString().Replace("foo", "mangledfoo")));
I quick hack, saves you little. But foo will not be present for the code awaiting it in nextpge.aspx :)
Interesting question. I don't see any real viable alternative to manually copying the collection since CopyTo will only allow you to get the values (and not the keys).
I think HollyStyles' Hack would work (although I would be nervous about putting a Replace in a QueryString - obv. dependant on use case), but there is one thing thats bothering me..
If the target page is not reading it, why do you need to remove it from the QueryString?
It will just be ignored?
Failing that, I think you would just need to bite the bullet and create a util method to alter the collection for you.
UPDATE - Following Response from OP
Ahhhh! I see now, yes, I have had similar problems with SiteMap performing full comparison of the string.
Since changing the other source code (i.e. the search) is out of the question, I would probably say it may be best to do a Replace on the string. Although to be fair, if you often encounter code similar to this, it would equally be just as quick to set up a utility function to clone the collection, taking an array of values to filter from it.
This way you would never have to worry about such issues again :)
HttpUtility.ParseQueryString(Request.Url.Query) return isQueryStringValueCollection. It is inherit from NameValueCollection.
var qs = HttpUtility.ParseQueryString(Request.Url.Query);
qs.Remove("foo");
string url = "~/Default.aspx";
if (qs.Count > 0)
url = url + "?" + qs.ToString();
Response.Redirect(url);
The search page appends "&terms=" to the query string for highlighting, so
it messes it up.
Only other option is a regex replace. If you know for sure that &terms is in the middle of the collection somewhere leave the trailing & in the regex, if you know for sure it's on the end then drop the trailing & and change the replacement string "&" to String.Empty
Response.Redirect(String.Format("nextpage.aspx?{0}", Regex.Replace(Request.QueryString.ToString(), "&terms=.*&", "&"));
Request.Url.GetLeftPart(UriPartial.Path) should do this
I found this was a more elegant solution
var qs = HttpUtility.ParseQueryString(Request.QueryString.ToString());
qs.Remove("item");
Console.WriteLine(qs.ToString());
Here is a solution that uses LINQ against the Request.QueryString which allows for complex filtering of qs params if required.
The example below shows me filtering out the uid param to create a relative url ready for redirection.
Before: http://www.domain.com/home.aspx?color=red&uid=1&name=bob
After: ~/home.aspx?color=red&name=bob
Remove QS param from url
var rq = this.Request.QueryString;
var qs = string.Join("&", rq.Cast<string>().Where(k => k != "uid").Select(k => string.Format("{0}={1}", k, rq[k])).ToArray());
var url = string.Concat("~", Request.RawUrl.Split('?')[0], "?", qs);
Can you clone the collection and then redirect to the page with the cloned (and modified) collection?
I know it's not much better than iterating...