Skip to content
This repository was archived by the owner on Feb 29, 2020. It is now read-only.

Commit c9d8e39

Browse files
committed
[client][managed][offline] Support relative and absolute uri in pull same as table.read
1 parent de19f92 commit c9d8e39

12 files changed

+247
-21
lines changed

sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Http/HttpUtility.cs

+39
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,45 @@ namespace Microsoft.WindowsAzure.MobileServices
1010
{
1111
internal static class HttpUtility
1212
{
13+
14+
/// <summary>
15+
/// Tries to parse the query as relative or absolute uri
16+
/// </summary>
17+
/// <param name="applicationUri">The application uri to use as base</param>
18+
/// <param name="query">The query string that may be relative path starting with slash or absolute uri</param>
19+
/// <param name="uri">The uri in case it was relative path or absolute uri</param>
20+
/// <returns>True if it was relative or absolute uri, False otherwise</returns>
21+
public static bool TryParseQueryUri(Uri applicationUri, string query, out Uri uri, out bool absolute)
22+
{
23+
if (query.StartsWith("/") && Uri.TryCreate(applicationUri, query, out uri))
24+
{
25+
absolute = false;
26+
return true;
27+
}
28+
else if (Uri.TryCreate(query, UriKind.Absolute, out uri))
29+
{
30+
if (uri.Host != applicationUri.Host)
31+
{
32+
throw new ArgumentException(Resources.MobileServiceTable_QueryUriHostIsDifferent);
33+
}
34+
35+
absolute = true;
36+
return true;
37+
}
38+
else
39+
{
40+
absolute = false;
41+
return false;
42+
}
43+
}
44+
45+
/// Returns the complete uri excluding the query part
46+
public static string GetUriWithoutQuery(Uri uri)
47+
{
48+
string path = uri.GetComponents(UriComponents.Scheme | UriComponents.UserInfo | UriComponents.Host | UriComponents.Port | UriComponents.Path, UriFormat.UriEscaped);
49+
return path;
50+
}
51+
1352
/// <summary>
1453
/// Parses a query string into a <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/>
1554
/// </summary>

sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Resources.Designer.cs

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Resources.resx

+3
Original file line numberDiff line numberDiff line change
@@ -426,4 +426,7 @@
426426
<data name="MobileServiceSyncTable_OrderByNotAllowed" xml:space="preserve">
427427
<value>The supported table options does not include orderby.</value>
428428
</data>
429+
<data name="MobileServiceTable_QueryUriHostIsDifferent" xml:space="preserve">
430+
<value>The query uri must be on the same host as the Mobile Service.</value>
431+
</data>
429432
</root>

sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/MobileServiceTable.cs

+7-8
Original file line numberDiff line numberDiff line change
@@ -149,15 +149,14 @@ internal virtual async Task<QueryResult> ReadAsync(string query, IDictionary<str
149149

150150
string uriPath;
151151
Uri uri;
152-
if (query.StartsWith("/") && Uri.TryCreate(this.MobileServiceClient.ApplicationUri, query, out uri))
152+
bool absolute;
153+
if (HttpUtility.TryParseQueryUri(this.MobileServiceClient.ApplicationUri, query, out uri, out absolute))
153154
{
154-
uriPath = uri.GetComponents(UriComponents.Scheme | UriComponents.UserInfo | UriComponents.Host | UriComponents.Port | UriComponents.Path, UriFormat.UriEscaped);
155-
query = uri.Query;
156-
}
157-
else if (Uri.TryCreate(query, UriKind.Absolute, out uri))
158-
{
159-
features |= MobileServiceFeatures.ReadWithLinkHeader;
160-
uriPath = uri.GetComponents(UriComponents.Scheme | UriComponents.UserInfo | UriComponents.Host | UriComponents.Port | UriComponents.Path, UriFormat.UriEscaped);
155+
if (absolute)
156+
{
157+
features |= MobileServiceFeatures.ReadWithLinkHeader;
158+
}
159+
uriPath = HttpUtility.GetUriWithoutQuery(uri);
161160
query = uri.Query;
162161
}
163162
else

sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/Query/MobileServiceTableQueryDescription.cs

+32-5
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ public MobileServiceTableQueryDescription(string tableName)
3939
this.Ordering = new List<OrderByNode>();
4040
}
4141

42+
/// <summary>
43+
/// Gets the uri path if the query was an absolute or relative uri
44+
/// </summary>
45+
internal string UriPath { get; private set; }
46+
4247
/// <summary>
4348
/// Gets or sets the name of the table being queried.
4449
/// </summary>
@@ -188,6 +193,27 @@ public string ToODataString()
188193
public static MobileServiceTableQueryDescription Parse(string tableName, string query)
189194
{
190195
query = query ?? String.Empty;
196+
197+
return Parse(tableName, query, null);
198+
}
199+
200+
internal static MobileServiceTableQueryDescription Parse(Uri applicationUri, string tableName, string query)
201+
{
202+
query = query ?? String.Empty;
203+
string uriPath = null;
204+
Uri uri;
205+
bool absolute;
206+
if (HttpUtility.TryParseQueryUri(applicationUri, query, out uri, out absolute))
207+
{
208+
query = uri.Query.Length > 0 ? uri.Query.Substring(1) : String.Empty;
209+
uriPath = uri.AbsolutePath;
210+
}
211+
212+
return Parse(tableName, query, uriPath);
213+
}
214+
215+
private static MobileServiceTableQueryDescription Parse(string tableName, string query, string uriPath)
216+
{
191217
bool includeTotalCount = false;
192218
int? top = null;
193219
int? skip = null;
@@ -231,23 +257,24 @@ public static MobileServiceTableQueryDescription Parse(string tableName, string
231257
}
232258
}
233259

234-
var queryDescription = new MobileServiceTableQueryDescription(tableName)
260+
var description = new MobileServiceTableQueryDescription(tableName)
235261
{
236262
IncludeTotalCount = includeTotalCount,
237263
Skip = skip,
238264
Top = top
239265
};
266+
description.UriPath = uriPath;
240267
if (selection != null)
241268
{
242-
((List<string>)queryDescription.Selection).AddRange(selection);
269+
((List<string>)description.Selection).AddRange(selection);
243270
}
244271
if (orderings != null)
245272
{
246-
((List<OrderByNode>)queryDescription.Ordering).AddRange(orderings);
273+
((List<OrderByNode>)description.Ordering).AddRange(orderings);
247274
}
248-
queryDescription.Filter = filter;
275+
description.Filter = filter;
249276

250-
return queryDescription;
277+
return description;
251278
}
252279
}
253280
}

sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/Sync/MobileServiceSyncContext.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ public async Task PullAsync(string tableName, string queryKey, string query, Mob
162162
await this.EnsureInitializedAsync();
163163

164164
var table = await this.GetTable(tableName);
165-
var queryDescription = MobileServiceTableQueryDescription.Parse(tableName, query);
165+
var queryDescription = MobileServiceTableQueryDescription.Parse(this.client.ApplicationUri, tableName, query);
166+
167+
166168
// local schema should be same as remote schema otherwise push can't function
167169
if (queryDescription.Selection.Any() || queryDescription.Projections.Any())
168170
{

sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/Sync/Queue/Actions/PullAction.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,12 @@ protected async override Task ProcessTableAsync()
5454
{
5555
this.CancellationToken.ThrowIfCancellationRequested();
5656

57-
string odata = this.Query.ToODataString();
58-
result = await this.Table.ReadAsync(odata, MobileServiceTable.IncludeDeleted(parameters), this.Table.Features);
57+
string query = this.Query.ToODataString();
58+
if (this.Query.UriPath != null)
59+
{
60+
query = MobileServiceUrlBuilder.CombinePathAndQuery(this.Query.UriPath, query);
61+
}
62+
result = await this.Table.ReadAsync(query, MobileServiceTable.IncludeDeleted(parameters), this.Table.Features);
5963
await this.ProcessAll(result.Values); // process the first batch
6064

6165
result = await FollowNextLinks(result);

sdk/Managed/test/Microsoft.WindowsAzure.MobileServices.Test/Microsoft.WindowsAzure.Mobile.Test.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<!-- A reference to the entire .NET Framework is automatically included -->
4444
<None Include="packages.config" />
4545
<Compile Include="SerializationTypes\TypeWithConstructor.cs" />
46+
<Compile Include="UnitTests\Http\HttpUtilityTests.cs" />
4647
<Compile Include="UnitTests\OData\ODataExpressionParser.Test.cs" />
4748
<Compile Include="UnitTests\Table\Sync\MobileServiceSyncContext.Test.cs" />
4849
</ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using System;
2+
using Microsoft.WindowsAzure.MobileServices.TestFramework;
3+
4+
namespace Microsoft.WindowsAzure.MobileServices.Test
5+
{
6+
[Tag("unit")]
7+
[Tag("http")]
8+
public class HttpUtilityTests : TestBase
9+
{
10+
11+
[TestMethod]
12+
public static void TryParseQueryUri_ReturnsTrue_WhenQueryIsRelativeOrAbsoluteUri()
13+
{
14+
var data = new[]
15+
{
16+
new
17+
{
18+
ServiceUri = "http://www.test.com",
19+
Query = "/about?$filter=a eq b&$orderby=c",
20+
Absolute = false,
21+
Result = "http://www.test.com/about?$filter=a eq b&$orderby=c"
22+
},
23+
new
24+
{
25+
ServiceUri = "http://www.test.com/",
26+
Query = "http://www.test.com/about?$filter=a eq b&$orderby=c",
27+
Absolute = true,
28+
Result = "http://www.test.com/about?$filter=a eq b&$orderby=c"
29+
}
30+
};
31+
32+
foreach (var item in data)
33+
{
34+
Uri result;
35+
bool absolute;
36+
Assert.IsTrue(HttpUtility.TryParseQueryUri(new Uri(item.ServiceUri), item.Query, out result, out absolute));
37+
Assert.AreEqual(absolute, item.Absolute);
38+
AssertEx.QueryEquals(result.AbsoluteUri, item.Result);
39+
}
40+
}
41+
42+
[TestMethod]
43+
public static void TryParseQueryUri_ReturnsFalse_WhenQueryIsNotRelativeOrAbsoluteUri()
44+
{
45+
var data = new[]
46+
{
47+
new
48+
{
49+
ServiceUri = "http://www.test.com",
50+
Query = "about?$filter=a eq b&$orderby=c",
51+
Result = "http://www.test.com/about?$filter=a eq b&$orderby=c"
52+
},
53+
new
54+
{
55+
ServiceUri = "http://www.test.com/",
56+
Query = "$filter=a eq b&$orderby=c",
57+
Result = "http://www.test.com/about?$filter=a eq b&$orderby=c"
58+
}
59+
};
60+
61+
foreach (var item in data)
62+
{
63+
Uri result;
64+
bool absolute;
65+
Assert.IsFalse(HttpUtility.TryParseQueryUri(new Uri(item.ServiceUri), item.Query, out result, out absolute));
66+
Assert.IsFalse(absolute);
67+
Assert.IsNull(result);
68+
}
69+
}
70+
71+
[TestMethod]
72+
public void GetUriWithoutQuery_ReturnsUriWithPath()
73+
{
74+
Tuple<string, string>[] input = new[]
75+
{
76+
Tuple.Create("http://contoso.com/asdf?$filter=3", "http://contoso.com/asdf"),
77+
Tuple.Create("http://contoso.com/asdf/def?$filter=3", "http://contoso.com/asdf/def"),
78+
Tuple.Create("https://contoso.com/asdf/def?$filter=3", "https://contoso.com/asdf/def")
79+
};
80+
81+
foreach (var item in input)
82+
{
83+
AssertEx.QueryEquals(HttpUtility.GetUriWithoutQuery(new Uri(item.Item1)), item.Item2);
84+
}
85+
}
86+
}
87+
}

sdk/Managed/test/Microsoft.WindowsAzure.MobileServices.Test/UnitTests/Table/MobileServiceTable.Generic.Test.cs

+13-2
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,21 @@ public async Task ReadAsync_WithAbsoluteUri_Generic()
2929

3030
IMobileServiceTable<ToDo> table = service.GetTable<ToDo>();
3131

32-
await table.ReadAsync<ToDo>("http://wwww.contoso.com/about?$filter=a eq b&$orderby=c");
32+
await table.ReadAsync<ToDo>("http://www.test.com/about?$filter=a eq b&$orderby=c");
3333

3434
Assert.AreEqual("TT,LH", hijack.Request.Headers.GetValues("X-ZUMO-FEATURES").First());
35-
Assert.AreEqual("http://wwww.contoso.com/about?$filter=a eq b&$orderby=c", hijack.Request.RequestUri.ToString());
35+
Assert.AreEqual("http://www.test.com/about?$filter=a eq b&$orderby=c", hijack.Request.RequestUri.ToString());
36+
}
37+
38+
[AsyncTestMethod]
39+
public async Task ReadAsync_Throws_IfAbsoluteUriHostNameDoesNotMatch()
40+
{
41+
IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", new TestHttpHandler());
42+
IMobileServiceTable<ToDo> table = service.GetTable<ToDo>();
43+
44+
var ex = await AssertEx.Throws<ArgumentException>(async () => await table.ReadAsync<ToDo>("http://www.contoso.com/about?$filter=a eq b&$orderby=c"));
45+
46+
Assert.AreEqual(ex.Message, "The query uri must be on the same host as the Mobile Service.");
3647
}
3748

3849
[AsyncTestMethod]

sdk/Managed/test/Microsoft.WindowsAzure.MobileServices.Test/UnitTests/Table/MobileServiceTable.Test.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -230,10 +230,10 @@ public async Task ReadAsync_WithAbsoluteUri()
230230

231231
IMobileServiceTable table = service.GetTable("someTable");
232232

233-
await table.ReadAsync("http://wwww.contoso.com/about/?$filter=a eq b&$orderby=c");
233+
await table.ReadAsync("http://www.test.com/about/?$filter=a eq b&$orderby=c");
234234

235235
Assert.AreEqual("TU,LH", hijack.Request.Headers.GetValues("X-ZUMO-FEATURES").First());
236-
Assert.AreEqual("http://wwww.contoso.com/about/?$filter=a eq b&$orderby=c", hijack.Request.RequestUri.ToString());
236+
Assert.AreEqual("http://www.test.com/about/?$filter=a eq b&$orderby=c", hijack.Request.RequestUri.ToString());
237237
}
238238

239239
[AsyncTestMethod]

sdk/Managed/test/Microsoft.WindowsAzure.MobileServices.Test/UnitTests/Table/Sync/MobileServiceSyncTable.Generic.Test.cs

+45-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// ----------------------------------------------------------------------------
44

55
using System;
6-
using System.Collections;
76
using System.Collections.Generic;
87
using System.Linq;
98
using System.Net;
@@ -330,6 +329,51 @@ public async Task PullAsync_UsesSkipAndTakeThenFollowsLinkThenUsesSkipAndTake()
330329
"http://www.test.com/tables/stringId_test_table?$skip=9&$top=45&__includeDeleted=true&__systemproperties=__version%2C__deleted");
331330
}
332331

332+
[AsyncTestMethod]
333+
public async Task PullAsync_Supports_AbsoluteAndRelativeUri()
334+
{
335+
var data = new string[]
336+
{
337+
"http://www.test.com/api/todoitem",
338+
"/api/todoitem"
339+
};
340+
341+
foreach (string uri in data)
342+
{
343+
var hijack = new TestHttpHandler();
344+
hijack.AddResponseContent("[{\"id\":\"abc\",\"String\":\"Hey\"},{\"id\":\"def\",\"String\":\"How\"}]"); // first page
345+
hijack.AddResponseContent("[]");
346+
347+
var store = new MobileServiceLocalStoreMock();
348+
IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
349+
await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());
350+
351+
IMobileServiceSyncTable<ToDoWithStringId> table = service.GetSyncTable<ToDoWithStringId>();
352+
353+
Assert.IsFalse(store.TableMap.ContainsKey("stringId_test_table"));
354+
355+
await table.PullAsync(uri);
356+
357+
Assert.AreEqual(store.TableMap["stringId_test_table"].Count, 2);
358+
359+
AssertEx.MatchUris(hijack.Requests, "http://www.test.com/api/todoitem?$skip=0&$top=50&__includeDeleted=true&__systemproperties=__version%2C__deleted",
360+
"http://www.test.com/api/todoitem?$skip=2&$top=50&__includeDeleted=true&__systemproperties=__version%2C__deleted");
361+
362+
Assert.AreEqual("QS,OL", hijack.Requests[0].Headers.GetValues("X-ZUMO-FEATURES").First());
363+
Assert.AreEqual("QS,OL", hijack.Requests[1].Headers.GetValues("X-ZUMO-FEATURES").First());
364+
}
365+
}
366+
367+
public async Task PullASync_Throws_IfAbsoluteUriHostNameDoesNotMatch()
368+
{
369+
IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", new TestHttpHandler());
370+
IMobileServiceSyncTable<ToDo> table = service.GetSyncTable<ToDo>();
371+
372+
var ex = await AssertEx.Throws<ArgumentException>(async () => await table.PullAsync("http://www.contoso.com/about?$filter=a eq b&$orderby=c"));
373+
374+
Assert.AreEqual(ex.Message, "The query uri must be on the same host as the Mobile Service.");
375+
}
376+
333377
[AsyncTestMethod]
334378
public async Task PullAsync_DoesNotFollowLink_IfRelationIsNotNext()
335379
{

0 commit comments

Comments
 (0)