private void ContentLoader_BeginLoad_Callback(IAsyncResult result) { DependencyObject content = null; Uri uriBeingLoaded = null; try { NavigationOperation asyncNavigationOperationCompleted = result.AsyncState as NavigationOperation; NavigationOperation navOp = this._currentNavigation; if (navOp == null || navOp.Uri != asyncNavigationOperationCompleted.Uri) { // We already fired NavigationStopped in NavigateCore(), so just return without doing anything return; } uriBeingLoaded = navOp.UriBeforeMapping; content = this._contentLoader.EndLoad(result) as DependencyObject; // If the content is anything but a UserControl, we should throw. // We support UserControls as they are a typical thing created in designers such as Blend, // but for a full experience one would use Page to get to things like NavigationContext, // NavigationService, Title, etc. if (!(content is UserControl)) { throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resource.NavigationService_ContentIsNotAUserControl, content == null ? "null" : content.GetType().ToString(), "System.Windows.Controls.UserControl")); } // Content loader was successful, so complete navigation // Create a new navigation context JournalEntry.SetNavigationContext(content, new NavigationContext(UriParsingHelper.InternalUriParseQueryStringToDictionary(asyncNavigationOperationCompleted.Uri, true /* decodeResults */))); content.SetValue(NavigationServiceProperty, this); // Complete navigation operation this.CompleteNavigation(content); } catch (Exception ex) { if (this.RaiseNavigationFailed(uriBeingLoaded, ex)) { throw; } } }
internal bool CheckForDeeplinks() { if (this.UseNavigationState) { string currentState = UriParsingHelper.InternalUriFromExternalValue(Application.Current.Host.NavigationState); if (!String.IsNullOrEmpty(currentState)) { this.AddHistoryPointIfDifferent(currentState); return(true); } } return(false); }
private void Initialize() { // Initialize stuff for the Uri template Regex newFromRegex = null; this._uriRegexIdentifierUsedTwice = this.UriTemplateContainsSameIdentifierTwice(this._uri, out newFromRegex); this._uriHasQueryString = !String.IsNullOrEmpty(UriParsingHelper.InternalUriGetQueryString(this._uri)); this._uriHasFragment = !String.IsNullOrEmpty(UriParsingHelper.InternalUriGetFragment(this._uri)); this._uriRegex = newFromRegex; this._mappedUriIsOnlyFragment = UriParsingHelper.InternalUriIsFragment(this._mappedUri); this._mappedUriIsOnlyQueryString = UriParsingHelper.QueryStringDelimiter + UriParsingHelper.InternalUriGetQueryString(this._mappedUri) == this._mappedUri.OriginalString; // Initialize stuff for the mapped Uri template this.GetIdentifiersForMappedUri(this._mappedUri); this._initialized = true; }
/// <summary> /// Conditionally adds a new history point if the new state information differs from the current journal entry Uri value. /// </summary> /// <param name="newState">An updated state value to examine.</param> private void AddHistoryPointIfDifferent(string newState) { // Check if different from our current state string currentState = String.Empty; if (this.CurrentEntry != null && this.CurrentEntry.Source != null) { currentState = UriParsingHelper.InternalUriFromExternalValue(this.CurrentEntry.Source.OriginalString); } if (string.Equals(newState, currentState, StringComparison.Ordinal) == false) { this._suppressNavigationEvent = true; this.AddHistoryPoint(new JournalEntry(string.Empty, new Uri(newState, UriKind.RelativeOrAbsolute))); this._suppressNavigationEvent = false; } }
/// <summary> /// Updates NavigationState to reflect the Journal state, if the Journal is using NavigationState. /// </summary> /// <param name="journalEntry">JournalEntry used to update the browser location.</param> private void UpdateNavigationState(JournalEntry journalEntry) { if (this.UseNavigationState) { if (this._suppressNavigationEvent == false) { string state = journalEntry.Source == null ? string.Empty : UriParsingHelper.InternalUriToExternalValue(journalEntry.Source); Application.Current.Host.NavigationState = state; if (HtmlPage.IsEnabled) { HtmlPage.Document.SetProperty("title", journalEntry.Name); } } } }
private bool NavigateCore_StartNavigation(Uri uri, NavigationMode mode, bool suppressJournalAdd, Uri mergedUriAfterMapping, Uri mergedUri, bool isFragmentNavigationOnly) { this._currentNavigation = new NavigationOperation(mergedUriAfterMapping, mergedUri, uri, mode, suppressJournalAdd); if (isFragmentNavigationOnly) { // If we're navigating only to a fragment (e.g. "#frag2") then the Uri to journal should be that merged with the base uri if (UriParsingHelper.InternalUriIsFragment(uri)) { this._currentNavigation.UriForJournal = mergedUri; } this.Host.Dispatcher.BeginInvoke(() => this.CompleteNavigation(null)); return(true); } this.UpdateNavigationCacheModeAlwaysPages(); string uriAllButFragment = UriParsingHelper.InternalUriGetAllButFragment(uri); Page reusedPage = null; if (this._cacheRequiredPages.ContainsKey(uriAllButFragment)) { reusedPage = this._cacheRequiredPages[uriAllButFragment]; } else if (this.Cache.Contains(uriAllButFragment)) { reusedPage = this.Cache[uriAllButFragment]; } // If a page was found in either cache and that page hasn't yet changed its NavigationCacheMode to Disabled, // then navigation is done, otherwise open up new content if (reusedPage != null && reusedPage.NavigationCacheMode != NavigationCacheMode.Disabled) { this.Host.Dispatcher.BeginInvoke(() => this.CompleteNavigation(reusedPage)); } else { this._currentNavigation.AsyncResult = this._contentLoader.BeginLoad(mergedUriAfterMapping, this._currentSourceAfterMapping, this.ContentLoader_BeginLoad_Callback, this._currentNavigation); } return(true); }
/// <summary> /// Updates NavigationState to reflect the Journal state, if the Journal is using NavigationState. /// </summary> /// <param name="journalEntry">JournalEntry used to update the browser location.</param> private void UpdateNavigationState(JournalEntry journalEntry) { if (this.UseNavigationState) { if (this._suppressNavigationEvent == false) { string state = journalEntry.Source == null ? string.Empty : UriParsingHelper.InternalUriToExternalValue(journalEntry.Source); // Title updates only occur when DOM access is enabled, so check this first. if (HtmlPage.IsEnabled) { // In older versions of IE (6, 7, and 8 in 7 compat mode) we use an // iframe to cause journal updates. But this requires that the title // be set before the navigation for the dropdowns for back/forward // to show the correct titles at the correct places in these lists. // // In newer versions of IE, and in all other supported browsers, the // title should be set after the navigation for correct behavior. if (UsingIFrame()) { HtmlPage.Document.SetProperty("title", journalEntry.Name); Application.Current.Host.NavigationState = state; } else { Application.Current.Host.NavigationState = state; HtmlPage.Document.SetProperty("title", journalEntry.Name); } } else { // We don't have DOM access, so just update NavigationState // without a title update Application.Current.Host.NavigationState = state; } } } }
private static void BeginLoad_OnUIThread(AsyncCallback userCallback, PageResourceContentLoaderAsyncResult result) { if (result.Exception != null) { result.IsCompleted = true; userCallback(result); return; } try { string pagePathAndName = UriParsingHelper.InternalUriGetBaseValue(result.Uri); string xaml = GetLocalXaml(pagePathAndName); if (String.IsNullOrEmpty(xaml)) { result.Exception = new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, Resource.PageResourceContentLoader_NoXAMLWasFound, pagePathAndName)); return; } string classString = GetXClass(xaml); if (String.IsNullOrEmpty(classString)) { try { result.Content = XamlReader.Load(xaml); } catch (Exception ex) { result.Exception = new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, Resource.PageResourceContentLoader_XAMLWasUnloadable, pagePathAndName), ex); return; } } else { // If it does have an x:Class attribute, then it has a // code-behind, so get the CLR type of the XAML instead. Type t = GetTypeFromAnyLoadedAssembly(classString); if (t == null) { result.Exception = new InvalidOperationException(String.Format( CultureInfo.CurrentCulture, Resource.PageResourceContentLoader_TheTypeSpecifiedInTheXClassCouldNotBeFound, classString, pagePathAndName)); return; } result.Content = Activator.CreateInstance(t); return; } } catch (Exception ex) { result.Exception = ex; } finally { result.IsCompleted = true; if (userCallback != null) { userCallback(result); } } }
private void CompleteNavigation(DependencyObject content) { Uri uri = null; string pageTitle = null; Page existingContentPage = this._host.Content as Page; Page newContentPage = content as Page; pageTitle = JournalEntry.GetName(content ?? this._host.Content as DependencyObject); NavigationOperation navOp = this._currentNavigation; this._currentNavigation = null; if (navOp != null) { // Set uri uri = navOp.UriBeforeMapping; // Used to suppress navigation notifications. navOp.SuppressNotifications = true; if (this.CurrentSource == navOp.UriForJournal) { // Do not record the navigation in the journal when moving to the same URI whether this // is a redirection or not. navOp.SuppressJournalAdd = true; } this.CurrentSource = navOp.UriForJournal; this._source = navOp.UriBeforeMapping; this._currentSourceAfterMapping = navOp.Uri; this.Host.UpdateSourceFromNavigationService(navOp.UriForJournal); this.Host.CurrentSource = this.CurrentSource; // Check if this is a 'New' operation if (navOp.Mode == NavigationMode.New && navOp.Uri != null && navOp.SuppressJournalAdd == false) { try { this._journalIsAddingHistoryPoint = true; JournalEntry je = new JournalEntry(pageTitle ?? uri.OriginalString, navOp.UriForJournal); this.Journal.AddHistoryPoint(je); } finally { this._journalIsAddingHistoryPoint = false; } } this.Host.CanGoBack = this.CanGoBack; this.Host.CanGoForward = this.CanGoForward; navOp.SuppressNotifications = false; } if (this.Journal.UseNavigationState && HtmlPage.IsEnabled) { HtmlPage.Document.SetProperty("title", pageTitle ?? (uri == null ? string.Empty : uri.OriginalString)); } if (content == null) { // We're navigating to a fragment in the current page, so for WPF compatibility, fire FragmentNavigation THEN Navigated if (navOp != null) { this.RaiseFragmentNavigation(UriParsingHelper.InternalUriGetFragment(navOp.Uri)); this.RaiseNavigated(content, uri, existingContentPage, newContentPage); } } else { // We're navigating to a fragment in the new content, so let the host load content, then for WPF compatibility, // fire Navigated THEN FragmentNavigation this.Host.Content = content; this.RaiseNavigated(content, uri, existingContentPage, newContentPage); string fragment = navOp == null ? null : UriParsingHelper.InternalUriGetFragment(navOp.Uri); if (!String.IsNullOrEmpty(fragment)) { this.RaiseFragmentNavigation(fragment); } } }
private bool NavigateCore(Uri uri, NavigationMode mode, bool suppressJournalAdd, bool isRedirect) { try { if (uri == null) { throw new ArgumentNullException("uri", Resource.NavigationService_NavigationToANullUriIsNotSupported); } // Make sure we're on the UI thread because of the DependencyProperties we use. if (!this.Host.Dispatcher.CheckAccess()) { // Move to UI thread this.Host.Dispatcher.BeginInvoke(() => this.NavigateCore(uri, mode, suppressJournalAdd, isRedirect)); return(true); } Uri mappedUri = uri; // If the Uri is only a fragment, mapping does not take place if (!UriParsingHelper.InternalUriIsFragment(uri)) { UriMapperBase mapper = this.Host.UriMapper; if (mapper != null) { Uri uriFromMapper = mapper.MapUri(uri); if (uriFromMapper != null && !String.IsNullOrEmpty(uriFromMapper.OriginalString)) { mappedUri = uriFromMapper; } else { mappedUri = uri; } } } Uri mergedUriAfterMapping = UriParsingHelper.InternalUriMerge(this._currentSourceAfterMapping, mappedUri) ?? mappedUri; Uri mergedUri = UriParsingHelper.InternalUriMerge(this._currentSource, uri) ?? uri; // If we're navigating to just a fragment (i.e. "#frag1") or to a page which differs only in the fragment // (i.e. "Page.xaml?id=123" to "Page.xaml?id=123#frag1") then complete navigation without involving the content loader bool isFragmentNavigationOnly = (mode != NavigationMode.Refresh) && (UriParsingHelper.InternalUriIsFragment(mappedUri) || UriParsingHelper.InternalUriGetAllButFragment(mergedUri) == UriParsingHelper.InternalUriGetAllButFragment(this._currentSource)); // Check to see if anyone wants to cancel if (mode == NavigationMode.New || mode == NavigationMode.Refresh) { if (this.RaiseNavigating(mergedUri, mode, isFragmentNavigationOnly) == true) { // Someone stopped us this.RaiseNavigationStopped(null, mergedUri); return(true); } } // If the ContentLoader cannot load the new URI, throw an ArgumentException if (!this.ContentLoader.CanLoad(mappedUri, _currentSourceAfterMapping)) { throw new ArgumentException(Resource.NavigationService_CannotLoadUri, "uri"); } if (isFragmentNavigationOnly && this.Host.Content == null) { // It doesn't make sense to fragment navigate when there's no content, so raise NavigationFailed throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resource.NavigationService_FragmentNavigationRequiresContent, "Frame")); } if (isRedirect && this._currentNavigation != null && this._currentNavigation.UriForJournal == this._currentSource) { // Do not record navigation in the journal in case of a redirection // where the original target is the current URI. suppressJournalAdd = true; } // Stop in-progress navigation this.StopLoadingCore(isRedirect); return(this.NavigateCore_StartNavigation(uri, mode, suppressJournalAdd, mergedUriAfterMapping, mergedUri, isFragmentNavigationOnly)); } catch (Exception ex) { if (this.RaiseNavigationFailed(uri, ex)) { throw; } return(true); } }
/// <summary> /// Attempts to process a Uri, if it matches the Uri template /// </summary> /// <param name="uri">The Uri to map</param> /// <returns>The Uri after mapping, or null if mapping did not succeed</returns> public Uri MapUri(Uri uri) { this.CheckPreconditions(); if (this._uriRegex == null) { // If an empty Uri was passed in, we can map that even with an empty Uri Template. if (uri == null || uri.OriginalString == null || uri.OriginalString.Length == 0) { return new Uri(this._mappedUri.OriginalString, UriKind.Relative); } // Otherwise, this does not match anything else { return null; } } string originalUriWithoutQueryString = UriParsingHelper.InternalUriGetBaseValue(uri); Match m = this._uriRegex.Match(originalUriWithoutQueryString); if (!m.Success) { return null; } string uriAfterMappingBase = UriParsingHelper.InternalUriGetBaseValue(this._mappedUri); IDictionary<string, string> uriAfterMappingQueryString = UriParsingHelper.InternalUriParseQueryStringToDictionary(this._mappedUri, false /* decodeResults */); IDictionary<string, string> originalQueryString = UriParsingHelper.InternalUriParseQueryStringToDictionary(uri, false /* decodeResults */); string originalFragment = UriParsingHelper.InternalUriGetFragment(uri); string uriAfterMappingFragment = UriParsingHelper.InternalUriGetFragment(this._mappedUri); // 'uriValues' is the values of the identifiers from the 'Uri' template, as they appear in the Uri // being processed IDictionary<string, string> uriValues = new Dictionary<string, string>(); // i begins at 1 because the group at index 0 is always equal to the parent's Match, // which we do not want. We only want explicitly-named groups. int groupCount = m.Groups.Count; for (int i = 1; i < groupCount; i++) { uriValues.Add(this._uriRegex.GroupNameFromNumber(i), m.Groups[i].Value); } foreach (string identifier in this._mappedUriIdentifiers) { string identifierWithBraces = "{" + identifier + "}"; string replacementValue = (uriValues.ContainsKey(identifier) ? uriValues[identifier] : String.Empty); // First check for identifiers in the base Uri, and replace them as appropriate uriAfterMappingBase = uriAfterMappingBase.Replace(identifierWithBraces, replacementValue); // Then, look through the query string (both the key and the value) and replace as appropriate string[] keys = new string[uriAfterMappingQueryString.Keys.Count]; uriAfterMappingQueryString.Keys.CopyTo(keys, 0); foreach (string key in keys) { // First check if the value contains it, as this is an easy replacement if (uriAfterMappingQueryString[key].Contains(identifierWithBraces)) { if (uriValues.ContainsKey(identifier)) { uriAfterMappingQueryString[key] = uriAfterMappingQueryString[key].Replace(identifierWithBraces, replacementValue); } } // If the key itself contains the identifier, then we need to remove the existing item with the key that // contains the identifier, and re-add to the dictionary with the new key and the pre-existing value if (key.Contains(identifierWithBraces)) { string existingVal = uriAfterMappingQueryString[key]; uriAfterMappingQueryString.Remove(key); uriAfterMappingQueryString.Add(key.Replace(identifierWithBraces, replacementValue), existingVal); } } // If there's an original fragment already present, it will always win, so don't bother doing replacements if (String.IsNullOrEmpty(originalFragment) && !String.IsNullOrEmpty(uriAfterMappingFragment)) { if (uriAfterMappingFragment.Contains(identifierWithBraces)) { uriAfterMappingFragment = uriAfterMappingFragment.Replace(identifierWithBraces, replacementValue); } } } foreach (string key in originalQueryString.Keys) { if (!uriAfterMappingQueryString.ContainsKey(key)) { uriAfterMappingQueryString.Add(key, originalQueryString[key]); } else { // If a value is present in the originally-navigated-to query string, it // takes precedence over anything in the aliased query string by default. uriAfterMappingQueryString[key] = originalQueryString[key]; } } if (!String.IsNullOrEmpty(originalFragment)) { uriAfterMappingFragment = originalFragment; } return UriParsingHelper.InternalUriCreateWithQueryStringValues(uriAfterMappingBase, uriAfterMappingQueryString, uriAfterMappingFragment); }
/// <summary> /// Tells whether or not the target Uri can be loaded /// </summary> /// <param name="targetUri">A URI to load</param> /// <param name="currentUri">The current URI</param> /// <returns>True if the targetURI can be loaded</returns> public bool CanLoad(Uri targetUri, Uri currentUri) { return(UriParsingHelper.InternalUriIsNavigable(targetUri)); }