I am having a lot of trouble clearing the WebView cache in my UWP app.
If I edit the content of a JS file linked from my HTML page, I can't get the change into my app unless I re-install the app.
The static WebView.ClearTemporaryWebDataAsync() method doesn't seem to work.
I have also tried adding headers to the request to disable caching:
private void reloadPage()
{
string url = getUrl();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(url));
request.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
request.Headers.Add("Pragma", "no-cache");
myWebView.NavigateWithHttpRequestMessage(request);
}
I also tried the following, on a punt (I'm not sure if this affects the WebView's caching behaviour), but still no joy:
private void onWebviewLoaded(object sender, RoutedEventArgs e)
{
Windows.Web.Http.Filters.HttpBaseProtocolFilter myFilter = new Windows.Web.Http.Filters.HttpBaseProtocolFilter();
myFilter.CacheControl.WriteBehavior = Windows.Web.Http.Filters.HttpCacheWriteBehavior.NoCache;
myFilter.CacheControl.ReadBehavior = Windows.Web.Http.Filters.HttpCacheReadBehavior.Default;
WebView.ClearTemporaryWebDataAsync().AsTask().Wait();
reloadPage();
}
Any help would be very much appreciated!
EDIT (14/12/15):
I have found that adding headers to the request (as in the first code example above) does work, but only if this has been in place for the lifetime of the app, since install. Which makes sense, as it's just saying not to cache this particular request - it could still use an old cached version.
This works as a cludge for now, but it would be much nicer to be able to make use of caching (e.g. for the duration of an app session), then later clear the cache (e.g. on next startup).
EDIT (14/07/16): The above approach doesn't seem to bear out. Caching behaviour seems to be erratic in the webview...
On a clean install of the app, I can see changes to CSS/JS files with absolutely NO code to clear/disable cache. Then at some seemingly arbitrary point, files seem to be cached, and I cannot clear them from the cache.
This finally works for me:
await WebView.ClearTemporaryWebDataAsync();
Windows.Web.Http.Filters.HttpBaseProtocolFilter myFilter = new Windows.Web.Http.Filters.HttpBaseProtocolFilter();
var cookieManager = myFilter.CookieManager;
HttpCookieCollection myCookieJar = cookieManager.GetCookies(new Uri("http://www.msftncsi.com/ncsi.txt"));
foreach (HttpCookie cookie in myCookieJar)
{
cookieManager.DeleteCookie(cookie);
}
I don't know if it helps but try to add a timestamp behind the url (url + "?=" + sometimestamphere)
Refreshing the webview seems to do a reload:
mainWebView.Refresh();
It's definitely a hack, but you may be able to insert at that some point in your application lifecycle to force reloading your content. Perhaps after the "mainWebView_NavigationCompleted()" event?
I had a similar problem with css in an UWP app not getting cleared. I ran ProcessMon and found that the UWP app was caching .css and .js files in Windows 10 at: C:\Users\\AppData\Local\Packages\microsoft.windows.authhost.sso.p_8wekyb3d8bbwe\AC\INetCache\Q8IHZDMV. I'm using the Web Authentication Broker so the scenario might be slightly different. If a location similar to this doesn't pan-out, you might want to try to run processmon (part of the SysInternals suite) when you start-up your UWP app to see if you can identify the path the UWP app is using for caching these assets. Once there, deleting these assets and restarting the app did the trick.
Related
I'm working on a fairly complex Xamarin.Forms application. We make a lot of REST requests. It's been reported that our application isn't respecting DNS failover for load balancing in a timely manner, so I started investigating. I'm running dnsmasq so I can look at when the app makes DNS requests. The code is currently using HttpWebRequest, and I noticed it's making DNS queries at least 10 minutes apart.
I understand part of this is because most .NET networking bits use a keepalive connection. I certainly see a higher rate of DNS queries if I force the headers to not use keepalive, but that adds network overhead so it's not a desirable solution. But I didn't initially see a clear way to control how HttpWebRequest makes DNS queries.
It looked promising that I could get its ServicePoint property and set the ConnectionLeaseTimeout on that. Unfortunately, that throws NotImplementedException in Xamarin so it's not going to be part of any solution.
I thought that perhaps HttpClient would be more configurable. I see a lot of discussion about how to use it properly, and that if you do it that way you need to set ServicePointManager.DnsRefreshTimeout to a smaller value for use cases where you want to expect DNS to update frequently. But this is usually done in conjunction with getting the ServicePoint for the deisred endpoint and also modifying ConnectionLeaseTimeout, which isn't possible.
I've been testing with a very simple app that reuses an HttpClient and makes the same request any time I push a button. Slap this ViewModel behind some Xaml with a button:
using System;
using Xamarin.Forms;
using System.Net.Http;
using System.Net;
namespace TestDns {
public class MainPageViewModel {
private const string _URL = "http://www.example.com";
private HttpClient _httpClient;
private ServicePoint _sp;
public MainPageViewModel() {
var sp = ServicePointManager.FindServicePoint(new Uri(_URL));
_sp = sp;
//_sp.ConnectionLeaseTimeout = 100; // throws NIE
_httpClient = new HttpClient();
ServicePointManager.DnsRefreshTimeout = 100;
}
public Command WhenButtonIsClicked {
get {
return new Command(() => SendRequest());
}
}
private async void SendRequest() {
Console.WriteLine($"{_sp.CurrentConnections}");
var url = "http://www.example.com";
var response = await _httpClient.GetAsync(url);
Console.WriteLine($"{response.Content}");
}
}
}
I didn't expect ConnectionLeaseTimeout to throw. I expected this code to only cache DNS requests for 100ms, I was going to choose a more reasonable timeframe like 2-3 minutes in more production-oriented tests. But since I can't get this simple example to function like I want, it seems moot to increase the delays.
Surely someone else has had this problem in a Xamarin app? Is my only solution going to be to look deeper and try to use native networking constructs?
If you're doing this on Android, DNS is cached for 10 minutes, and I don't believe you have any access to the expiration/refresh time from inside of your app. There are a number of ways to force a refresh but they all involve user actions like going into Network Connections and flipping from Static to DHCP and back, etc.
The only way I can think of to be sure of getting a fresh DNS lookup from inside your app is to have 10+ minutes worth of DNS entries that all alias to your server, and cycle your app through them, so every time you ask for a DNS lookup, it's a new name and not in the cache.
For example, look for 1.server.example.com 2.server.example.com, etc. Each new name will force a new lookup and won't be pulled from the cache because it's not there.
It seems that Java has decided the solution to "some people implement DNS improperly and ignore TTL" is to make the problem worse by ensuring devices that use Java implement DNS improperly. There is a single TTL used for all DNS entries in the cache. There's some philosophical debate and what led me to the answer in this question which I adapted for the answer.
In terms of Xamarin projects, add this somewhere (I chose early in MainActivity):
Java.Security.Security.SetProperty("networkaddress.cache.ttl", "<integer seconds>");
Replace "<integer seconds>" with your desired TTL for all DNS entries. Be aware lower values might mean you make more DNS queries than you used to, if you're seriously trying to save networking bytes this could be an issue.
I'm leaving Terry Carmen's answer selected as "the answer".
We previously had cookieless session enabled on our application. We have disabled this and gone to session cookies however we are having a problem. Users who had the session ID in their URL as a bookmark are still able to navigate to the site with the session id in the url. I have set it to not regenerate expired sessions but it is still allowing it anyways. It also ends up creating a session cookie in addition and then we are getting random session loss. I've come up with a few wonky workarounds like stripping it out using a URL rewrite and stripping it out via javascript but this seems bad. Is there anything built in that I am missing that can help with this? Not that it should matter for this but I will add we are using state server.
For anyone else looking for a solution that won't require users to update their bookmarks I was able to use the following in my Global.asax Application_BeginRequest:
void Application_BeginRequest(object sender, EventArgs e) {
if (CookielessValuesExist()) {
Response.Redirect(Request.Url.OriginalString, true);
}
}
private bool CookielessValuesExist() {
string cookieless = Request.Params["HTTP_ASPFILTERSESSIONID"];
if (string.IsNullOrWhiteSpace(cookieless)) {
return false;
}
return true;
}
A URL rewrite is a good solution to this.
However if you would like users to update their bookmarks, (so you can eventually retire the URL rewrite) you might consider having your URL rewrite send them to a page telling them so:
Oops! That link doesn't work.
And then giving them the usual options to log in etc.
I am currently developing an application for Windows Phone 10 using UWP, in which I need a WebView that will set up several cookies in the process. The WebView can be instantiated many times, and I have noticed that the cookies do not get cleared on multiple instantiations, which makes it a bit difficult for my workflow (say, for instance, a user has to log in using the WebView, then when re-instantiating, the old state will be there).
I have tried using the clearTemporaryWebDataAsync to now avail. What is worse is that I don't know in advance the domains associated to the cookies, so I cannot do something like this:
HttpBaseProtocolFilter myFilter = new HttpBaseProtocolFilter();
HttpCookieManager cookieManager = myFilter.CookieManager;
HttpCookieCollection myCookieJar = cookieManager.GetCookies(new Uri("https://url.com"));
foreach (HttpCookie cookie in myCookieJar)
{
cookieManager.DeleteCookie(cookie);
}
Is there anything in the API that might help me wipe out all the cookies for the WebView, or at least get a list of them so that then I can use DeleteCookie on each?
What is worse is that I don't know in advance the domains associated to the cookies, so I cannot do something like this:
AFAIK, there isn't a method for clearing cache data except ClearTemporaryWebDataAsync, but the code snippet you provided does clear cookies. For what you consider about don't know in advance the domains to the cookies, you may get the current navigating Uri by the WebViewNavigationStartingEventArgs of NavigationStarting event handle. For example,
private void WebViewControl2_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
{
Uri gotouri = args.Uri;
HttpBaseProtocolFilter myFilter = new HttpBaseProtocolFilter();
HttpCookieManager cookieManager = myFilter.CookieManager;
HttpCookieCollection myCookieJar = cookieManager.GetCookies(gotouri);
foreach (HttpCookie cookie in myCookieJar)
{
cookieManager.DeleteCookie(cookie);
}
}
And this will clear the cookies of current Uri. But this will clear the cookies every time the WebView navigates. You may need to set a flag to ensure if the WebView is a new instance depending on your app logic.
Another thing, in apps compiled for Windows 10, WebView uses the Microsoft Edge rendering engine to display HTML content. Clear cookies of current WebView instance may also clear others, which is same as you found cookies still exists when you have a new instance.
In order to log into a certain part of a website the users of my application require their cookie. To do this I need to grab it and pass it to url.
Does anyone know how to grab a certain websites cookie from the browser control?
I saw this method but wasn't quite clear.
Thanks, TP.
As of WP 7.1 Mango "release", if one may call it, please see the WebBrowser Control Overview for Windows Phone. It has been recently updated a little bit, and it turns out that they actually have added some support for cookie-retrieval from the WebBrowser. On the bottom of the page you will find a tiny link GetCookies(WebBrowser) pointing to description of a new class: WebBrowserExtensions with this very handy method. Yes, this class has only that one single member. It's an extension method, I suppose no explanations needed on that.
I have not played with this method much, but it seems that this will allow you to access the very same thing as the JS trick: the cookieset for the current URL. It probably will not allow to set anything, nor to peek cookies for other URLs. Maybe if you play hard with the CookieContainer you will receive, but I doubt.
On the 7.0 release, I've been struggling quite hard to achieve "cookie transparency" for my App. Long story short, my app was doing some background HTTP requests, and also had a WebBrowser to show some online content -- and "it would be great" if both sources of connections would emit the same cookies to the server.. And guess what, my application had to make the first request, then let the browser navigate. With such requirements, there was virtually is no way to achieve consistency of the cookies - bah, even with the current new and glorious GetCookie method, I suppose it would be damn hard. So, to the point - it was possible, but needed to use some hidden API, that is present publicitly on the Phone, but is hidden in the SDK. The API is available the (public) class System.Net.Browser.WebRequestCreator, freely available. The quirk is: in the SDK this class has a single public static property "IWebRequestCreate ClientHttp" with a method "Create" that you can use to "factory" your "raw http" connections - in case you dont want to use the WebClient for some reason. On the phone, and on the emulator, there is a second public static property called "IWebRequestCreate BrowserHttp", easily returned by Reflection:
PropertyInfo brwhttp = typeof(System.Net.Browser.WebRequestCreator)
.GetProperty("BrowserHttp")
with this property, you will be able to obtain a "special" internal instance of IWebRequestCreate that is used internally by the WebBrowser. By opening your background HTTP requests with this class, you will get your cookies automatically set as if they were created/sent by the WebBrowser control, but in turn - you will NOT be able to modify http headers, userprovide http user authentication and neither do a few lowlevel things - because all that settings will be synced with the WebBrowser's data stored for current 'system user instance', if I'm allowed to call it as such on the single-user Phone device heh. The interoperation between connections and the WebBrowser works both ways - if your HTTP connection (created with use of the 'hidden property') receives any settings/cookies/etc -- then the WebBrowser will instantly notice them and update its own cache. No cookie/session loss on neither of the sides!
If you need to passively get cookies for your subsequent connections after some first WebBrowser navigation - please use the GetCookie or the JS way.
But if you need your code to be first, and then pass authz to the WebBrowser -- you will probably have to dig deeper and use the above.. It's been hidden, so please resort to the other means first!
..and don't ask me how did I found it or how much time it took :P
have a nice fun with it
//edit: I've just found out, that the BrowserHttp property is a normal Silverlight's way to access the Browser's connection factory, please see BrowserHttp. It seems that it is only has been hidden in the 'miniSilverlight' written for the WP7 platform!
The approach being described in the post you linked is to use the WebBrowser control's InvokeScript method to run some javascript. However the post appears to use a "cookies" collection which doesn't actually exist.
string cookie = myWebBrowser.InvokeScript("document.cookie") as string;
Now for the hard part the string you get contains all pertinent cookie name/value pairs for the page with the values still being Url encoded. You will need to parse the returned string for the value you need.
See document.cookie property documentation.
Edit:
Looking at it fresh instead of relying on the post, InvokeScript invokes named function on the window of the host browser. Hence the page being displayed in the WebBrowser would itself need to include a function like:-
function getCookie() { return document.cookie; }
Then the InvokeScript would look like:-
string cookie = myWebBrowser.InvokeScript("getCookie");
As #quetzalcoatl already suggested, you can use internal instance of WebRequestCreator to share cookies between browser instances and instances of WebRequest. You don't get to access the cookies directly though, I think that's just a security measure by Microsoft.
This code below creates a WebReqeust object, connected to CookieContainer of WebBrowser instance. It then posts to a url to log in the user and store cookies in the container.
After it's done, all browser instances within the app instance will have required set of cookies.
var browser = new WebBrowser();
var brwhttp = typeof (WebRequestCreator).GetProperty("BrowserHttp");
var requestFactory = brwhttp.GetValue(browser, null) as IWebRequestCreate;
var uri = new Uri("https://www.login.com/login-handler");
var req = requestFactory.Create(uri);
req.Method = "POST";
var postParams = new Dictionary<string, string> {
{"username", "turtlepower"},
{"password": "ZoMgPaSSw0Rd1"}
};
req.BeginGetRequestStream(aReq => {
var webRequest = (HttpWebRequest)aReq.AsyncState;
using (var postStream = webRequest.EndGetRequestStream(aReq)) {
// Build your POST request here
var postDataBuilder = new StringBuilder();
foreach (var pair in paramsDict) {
if (postDataBuilder.Length != 0) {
postDataBuilder.Append("&");
}
postDataBuilder.AppendFormat("{0}={1}", pair.Key, HttpUtility.UrlEncode(pair.Value));
}
var bytes = Encoding.UTF8.GetBytes(postDataBuilder.ToString());
postStream.Write(bytes, 0, bytes.Length);
}
// Receive response
webRequest.BeginGetResponse(aResp => {
var webRequest2 = (HttpWebRequest) aResp.AsyncState;
webRequest = (HttpWebRequest)aResp.AsyncState;
string resp;
using (var response = (HttpWebResponse)webRequest2.EndGetResponse(aResp)) {
using (var streamResponse = response.GetResponseStream()) {
using (var streamReader = new System.IO.StreamReader(streamResponse)) {
resp = streamReader.ReadToEnd();
}
}
}
}, webRequest);
}, req);
One of the issues I couldn't solve though was exceptions thrown when server returns 302 - it seems to throw WebException error with "Not found" description.
// Ensure this is set to true BEFORE navigating to the page
webBrowser1.IsScriptEnabled = true;
// Once the page has loaded, you can read the cookie string
string cookieString = webBrowser1.InvokeScript("eval", new string[] { "document.cookie;" }) as string;
The cookieString variable will contain the full cookie for the document. You can then parse the string.
There is an WebBrowser Extension class which is exactly developed for this:
CookieCollection tempCookies = Microsoft.Phone.Controls.WebBrowserExtensions.GetCookies(this.BrowserControl);
I've been trying to get this to work. It's basically a way to have certain MVC pages work within a webforms cms (umbraco)
Someone tried it before me and had issues with MVC2.0 (see here), I have read the post, did what was announced there, but with or without that code, I seem to get stuck on a different matter.
It seems like, if I call an url, it fires the handler, but fails to request the querystring passed, the variable originalPath is always empty,
for example I call this url: http://localhost:8080/mvc.ashx?mvcRoute=/home/RSVPForm
the handler is supposed to get the mvcRoute but it is always empty. Thus gets rewritten to a simple / and then returns resource cannot be found error.
Here is the code I use now:
public void ProcessRequest(HttpContext httpContext)
{
string originalPath = httpContext.Request.Path;
string newPath = httpContext.Request.QueryString["mvcRoute"];
if (string.IsNullOrEmpty(newPath))
newPath = "/";
HttpContext.Current.RewritePath(newPath, false);
IHttpHandler ih = (IHttpHandler)new MvcHttpHandler();
ih.ProcessRequest(httpContext);
HttpContext.Current.RewritePath(originalPath, false);
}
I would like some new input on this as I'm staring myself blind on such a simple issue, while I thought I would have more problems with mvc itself.
have no time to investigate, but after copying the site over to different locations, using numerous web.config changes (unrelated to this error but was figuring other things out) this error seems to have solved itself. so its no longer an issue, however i have no clue as to what exactly made this to work again.
on a side note
ih.ProcessRequest(httpContext);
should have been,
ih.ProcessRequest(HttpContext.Current);