/// <summary> /// Checks that the content page does not contain invalid content. /// </summary> private List <Content> GetChildPageContents(DotvvmView childPage, List <ContentPlaceHolder> parentPlaceHolders) { // make sure that the body contains only whitespace and Content controls var nonContentElements = childPage.Children.Where(c => !((c is RawLiteral && ((RawLiteral)c).IsWhitespace) || (c is Content))); if (nonContentElements.Any()) { // show all error lines var innerExceptions = nonContentElements.Select(s => new Exception($"Error occurred near line: {(s.GetValue(Internal.MarkupLineNumberProperty)?.ToString() ?? "")}.")).ToList(); // the message cannot be specifically to the line, because MarkupLineNumber shows the last character position which is a line under the error in some cases. var corruptedFile = childPage.GetValue(Internal.MarkupFileNameProperty)?.ToString(); throw new AggregateException("If the page contains @masterpage directive, it can only contain white space and <dot:Content /> controls! \r\n" + (string.IsNullOrWhiteSpace(corruptedFile) ? "" : $"Corrupted file name: {corruptedFile}"), innerExceptions); } // make sure that the Content controls are not nested in other elements var contents = childPage.GetAllDescendants().OfType <Content>() .Where(c => !(bool)c.GetValue(Internal.IsMasterPageCompositionFinishedProperty)) .ToList(); if (contents.Any(c => c.Parent != childPage)) { throw new Exception("The control <dot:Content /> cannot be placed inside any control!"); // TODO: exception handling } return(contents); }
/// <summary> /// Performs the master page nesting. /// </summary> private void PerformMasterPageComposition(DotvvmView childPage, DotvvmView masterPage) { if (!masterPage.ViewModelType.IsAssignableFrom(childPage.ViewModelType)) { throw new DotvvmControlException(childPage, $"Master page requires viewModel of type '{masterPage.ViewModelType}' and it is not assignable from '{childPage.ViewModelType}'."); } // find content place holders var placeHolders = GetMasterPageContentPlaceHolders(masterPage); // find contents var contents = GetChildPageContents(childPage, placeHolders); // perform the composition foreach (var content in contents) { // find the corresponding placeholder var placeHolder = placeHolders.SingleOrDefault(p => p.ID == content.ContentPlaceHolderID); if (placeHolder == null) { throw new DotvvmControlException(content, $"The placeholder with ID '{content.ContentPlaceHolderID}' was not found in the master page '{masterPage.GetValue(Internal.MarkupFileNameProperty)}'!"); } // replace the contents var contentPlaceHolder = new PlaceHolder(); contentPlaceHolder.SetDataContextType(content.Parent.GetDataContextType()); (content.Parent as DotvvmControl)?.Children.Remove(content); placeHolder.Children.Clear(); placeHolder.Children.Add(contentPlaceHolder); contentPlaceHolder.Children.Add(content); content.SetValue(Internal.IsMasterPageCompositionFinishedProperty, true); content.SetValue(DotvvmView.DirectivesProperty, childPage.Directives); content.SetValue(Internal.MarkupFileNameProperty, childPage.GetValue(Internal.MarkupFileNameProperty)); } // copy the directives from content page to the master page (except the @masterpage) masterPage.ViewModelType = childPage.ViewModelType; }