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);
}
Related
I have an ILogger interface with LogTrace(string value, params object[] parameters). Now I want to verify that the LogTrace is called and the string to log contains some id. The problem is that it can be called differently. E.g.
1) LogTrace("MyString " + id)
2) LogTrace("MyString {0}", id)
and so on.
Is there a good way with Moq to verify all the scenarios? I can only think of creating a hand-made mock that will format the string that will be available for verification.
mock.Verify( m => m.LogTrace( It.IsAny<string>(), It.IsAny<object[]>() ) );
The params object[] is passed to the method as object[] anyway so you just have to match the array somehow (as above for example, this accepts anything).
If you need more control over the list, use the It.Is matcher which allows you to create your own predicate:
mock.Verify( m => m.LogTrace( It.IsAny<string>(),
It.Is<object[]>(ps =>
ps != null &&
ps.Length == 1 &&
ps[0] is int &&
(int)ps[0] == 5
) ) );
This example shows how to verify if the param list is not empty and contains 5 as the only parameter of type int.
I don't think there is an easy way for doing what you need here. The problem is that you need to ensure that a certain combination of values will be passed to your method, which results in many different verifiable scenarios:
String contains ID AND parameters contains ID - pass
String contains
ID AND parameters does not contain ID - pass
String does not contain
ID AND parameters contains ID = pass
String does not contain ID AND
parameters does not contain ID - fail
However, Moq does not support this sort of conditional expressions between different arguments of your verifiable method. One possible solution is to check for the absence of an id, instead of its presence in either argument. Try something like:
mock.Verify(m => m.LogTrace(
It.Is<string>(s => !s.Contains(id)),
It.Is<object[]>(o => !o.Contains(id))), Times.Never());
What we are doing here is verifying whether the fail condition is ever met - that is, your string does not contain an id, and neither does your object array. We use Times.Never() to make sure that this situation should never happen.
Keep in mind, however, that the code might not be obvious at first glance; make sure you properly explain your intent once you write it.
You could try to use the Callback, but it get fairly convoluted (not tested):
var mock = new Mock<ILogger>();
string trace = null;
mock.Setup(l => l.LogTrace(It.IsAny<string>(), It.IsAny<object[]>()))
.Callback((s1, par) =>
{
trace = string.Format(s1, par);
});
//rest of the test
Assert.AreEqual(expected, trace);
If ILogger has a small interface, you might consider implementing a stub manually (that would keep all the lines it is supposed to log), and verify that at the end of the test. If you have multiple lines being logged than this will be a more readable setup.
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?
How to allow null in Context.Request:
context.Response.Write(retrieveList(context.Request["SalCode"].ToString(null), context.Request["groupKeyword"].ToString(), context.Request["text"].ToString()));
Firstly, Request["..."] already returns a string, so there is no need to call ToString() on it and thus no need to worry, at this stage, if it returns null (i.e. if the key is not present in the request).
Thus you can call e.g.
retrieveList(
context.Request["SalCode"],
context.Request["groupKeyword"],
context.Request["text"]);
without worrying if any of the three are null.
You can then alter retrieveList to respond correctly if any of the inputs is null. For example, you could return null:
private string /*or whatever*/ retrieveList(
string salCode, string groupKeyword, string text)
{
if (String.IsNullOrEmpty(salCode) ||
String.IsNullOrEmpty(groupKeyword) ||
String.IsNullOrEmpty(text))
{
return null;
}
...
}
Then, note that Response.Write doesn't care if you give it a null, it just writes nothing, so you can keep the call to Write as above.
Alternatively, you could for example check the return value and write a message if it is null:
var list = retrieveList(
context.Request["SalCode"],
context.Request["groupKeyword"],
context.Request["text"]));
if (!String.IsNullOrEmpty(list))
{
context.Response.Write(list);
}
else
{
context.Response.Write("Missing request parameter.");
}
TYou didn't specify anything! Please add information, you even didn't specify the environment, the classes, or the general context in which this problem occurs. nor do we know what the signature of retrieveList() is! This makes us very difficult to help you! How much would you like to answer the question, one of my colleagues faced once: 'This crap doesn't work!' ? (Yes, it is not even a question, but happened in a real life support situatiuon!)
One thing I noted is that you use *T*oString() instead of *t*oString(), I assume that is a typo. (edited, as it is clear that this is C#) Also, I don't know what .toString(null) means. Did you want to tell us that that statement causes the NullPointerException? In that case, you could have committed a bit more effort towards us to understand your question, e.g. by writing this down...
BTW, if that's the case, I'd say, this will solve your problem:
Object salCode = context.Request["SalCode"];
context.Response.Write(retrieveList(salCode==null?"":salCode.ToString(), context.Request["groupKeyword"].ToString(), context.Request["text"].ToString()));
EDIT I think (but have no means to test) that if the null is the problem, this would fix it. If however, the underlying code does not work properly with empty String specified, that should be checked in retrieveList() like this (pasted from referenced post):
private string retrieveList(string SalCode, string groupKeyword, string text)
{
SqlConnection _sqlCon = default(SqlConnection);
SqlCommand _sqlCom = default(SqlCommand);
SqlDataReader _sqlReader = default(SqlDataReader);
StringBuilder _sb = new StringBuilder();
List<TokenInputJSON> _out = null;
try
{
_sqlCon = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["SalesianBlastConnectionString"].ConnectionString);
_sqlCom = new SqlCommand("getTokenInput", _sqlCon);
_sqlCom.CommandType = CommandType.StoredProcedure;
_sqlCon.Open();
//This is the edited part
if(SalCode==null || SalCode.Equals("")) {
_sqlCom.Parameters.Add(new SqlParameter("#salCode", SqlDbType.VarChar, 4)).Value = SalCode;
}
//... continue with the other parts
Quick check: I just had an idea: use a constant value when calling retrieveList to find out if this is the problem:
context.Response.Write(retrieveList("enterAValidSalCodeHere", context.Request["groupKeyword"].ToString(), context.Request["text"].ToString()));
My code:
public string LocName(string locID)
{
var name = (from a in idc.Locations
where a.ID.ToString() == locID
select a.Name).Single();
return name;
}
I need it to return the name of the Location that matches the ID as a string for a TextBlock. Instead it returns no elements, I have checked that the ID is correct so it should return at least one element.
I have also tried:
where a.ID == new Guid(locID)
as well as
.First()
.FirstOrDefault()
have tried returning the result as an element and databinding to that but still - no elements.
What is the error???
Must be a casing issue if the data is there, you should always use ToUpper() as that is optimised for string equality checks.
Also you should always use FirstOrDefault as Single will throw an exception if there are more than one matches and First will throw an exception if there are no matches. Try:
EDIT: I've also added Trim on the inputted value to sanitise the spaces. I've also added a null check on the locID parameter as that would blow up if it was passed is as null. Lastly, I added a ?? (coalesce) on the return statement just incase it is returning null and you're performing other things on that string (such as Trim or ToLowerCase) as that would result in an exception.
public string LocName(string locID)
{
if (locID == null) return string.Empty;
var name = (from a in idc.Locations
where a.ID.ToString().Trim().ToUpper() == locID.Trim().ToUpper()
select a.Name).FirstOrDefault();
return name ?? string.Empty;
}
Well firstly Single() will throw an exception if there are more than one matches to the query. I advise using FirstOrDefault() if you re expecting more than one answer back.
Also I would try using Trim() and ToUpper() to get rid of any whitespaces/case issues which might be preventing the strings from matching.
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...