Making it so when a content item is set as the home page it's only accessible as the home page and not at its path

- current implication for blogs is that if a blog is set as the home page its posts are then all rooted (e.g. if a blog at
  /blog is set as the home page then it's available at / and a post at, say, /my-post instead of /blog/my-post). There should
  maybe be a setting to alter the paths of the posts but (1) that's a pain at the moment, (2) hacking the URL by removing
  the post slug would result in a 404 instead of redirecting to /, the path of the blog - we don't handle redirects of that
  nature - and (3) the ability to root a blog in this manner *has* been requested by some.

--HG--
branch : dev
This commit is contained in:
Nathan Heskew
2010-12-01 00:51:42 -08:00
parent 2798850a38
commit 381f70d47c
14 changed files with 292 additions and 231 deletions

View File

@@ -58,14 +58,7 @@ namespace Orchard.Core.Routable.Controllers {
throw new ApplicationException(T("Ambiguous content").Text);
}
var item = hits.Single();
// primary action run for a home paged item shall not pass
if (!RouteData.DataTokens.ContainsKey("ParentActionViewContext")
&& item.Id == _routableHomePageProvider.GetHomePageId(_workContextAccessor.GetContext().CurrentSite.HomePage)) {
return HttpNotFound();
}
dynamic model = _contentManager.BuildDisplay(item);
dynamic model = _contentManager.BuildDisplay(hits.Single());
// Casting to avoid invalid (under medium trust) reflection over the protected View method and force a static invocation.
return View((object)model);
}

View File

@@ -16,6 +16,8 @@ namespace Orchard.Core.Routable.Handlers {
private readonly IOrchardServices _services;
private readonly IRoutablePathConstraint _routablePathConstraint;
private readonly IRoutableService _routableService;
private readonly IContentManager _contentManager;
private readonly IWorkContextAccessor _workContextAccessor;
private readonly IHomePageProvider _routableHomePageProvider;
public RoutePartHandler(
@@ -23,11 +25,14 @@ namespace Orchard.Core.Routable.Handlers {
IRepository<RoutePartRecord> repository,
IRoutablePathConstraint routablePathConstraint,
IRoutableService routableService,
IContentManager contentManager,
IWorkContextAccessor workContextAccessor,
IEnumerable<IHomePageProvider> homePageProviders) {
_services = services;
_routablePathConstraint = routablePathConstraint;
_routableService = routableService;
_contentManager = contentManager;
_workContextAccessor = workContextAccessor;
_routableHomePageProvider = homePageProviders.SingleOrDefault(p => p.GetProviderName() == RoutableHomePageProvider.Name);
T = NullLocalizer.Instance;
@@ -47,9 +52,26 @@ namespace Orchard.Core.Routable.Handlers {
OnPublished<RoutePart>((context, route) => {
FinalizePath(route, context, processSlug);
if (route.ContentItem.Id != 0 && route.PromoteToHomePage && _routableHomePageProvider != null) {
_services.WorkContext.CurrentSite.HomePage = _routableHomePageProvider.GetSettingValue(route.ContentItem.Id);
_routablePathConstraint.AddPath("");
if (route.Id != 0 && route.PromoteToHomePage && _routableHomePageProvider != null) {
var homePageSetting = _workContextAccessor.GetContext().CurrentSite.HomePage;
var currentHomePageId = !string.IsNullOrWhiteSpace(homePageSetting)
? _routableHomePageProvider.GetHomePageId(homePageSetting)
: 0;
if (currentHomePageId != route.Id) {
// reset the path on the current home page
var currentHomePage = _contentManager.Get(currentHomePageId);
if (currentHomePage != null)
FinalizePath(currentHomePage.As<RoutePart>(), context, processSlug);
// set the new home page
_services.WorkContext.CurrentSite.HomePage = _routableHomePageProvider.GetSettingValue(route.ContentItem.Id);
}
// readjust the constraints of the current current home page
_routablePathConstraint.RemovePath(route.Path);
route.Path = "";
_routableService.FixContainedPaths(route);
_routablePathConstraint.AddPath(route.Path);
}
});

View File

@@ -1,7 +1,5 @@
using System.Linq;
using Orchard.Blogs.Services;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Localization;
using Orchard.UI.Navigation;
@@ -32,12 +30,12 @@ namespace Orchard.Blogs {
}
else if (singleBlog != null)
menu.Add(T("Manage Blog"), "1.0",
item => item.Action("Item", "BlogAdmin", new { area = "Orchard.Blogs", blogSlug = singleBlog.As<IRoutableAspect>().Path }).Permission(Permissions.MetaListBlogs));
item => item.Action("Item", "BlogAdmin", new { area = "Orchard.Blogs", blogId = singleBlog.Id }).Permission(Permissions.MetaListBlogs));
if (singleBlog != null)
menu.Add(T("Create New Post"), "1.1",
item =>
item.Action("Create", "BlogPostAdmin", new { area = "Orchard.Blogs", blogSlug = singleBlog.As<IRoutableAspect>().Path }).Permission(Permissions.PublishBlogPost));
item.Action("Create", "BlogPostAdmin", new { area = "Orchard.Blogs", blogId = singleBlog.Id }).Permission(Permissions.PublishBlogPost));
menu.Add(T("Create New Blog"), "1.2",
item =>

View File

@@ -1,5 +1,4 @@
using System.Linq;
using System.Reflection;
using System.Web.Mvc;
using Orchard.Blogs.Extensions;
using Orchard.Blogs.Models;
@@ -50,7 +49,7 @@ namespace Orchard.Blogs.Controllers {
if (!Services.Authorizer.Authorize(Permissions.ManageBlogs, T("Not allowed to create blogs")))
return new HttpUnauthorizedResult();
BlogPart blog = Services.ContentManager.New<BlogPart>("Blog");
var blog = Services.ContentManager.New<BlogPart>("Blog");
if (blog == null)
return HttpNotFound();
@@ -81,11 +80,11 @@ namespace Orchard.Blogs.Controllers {
return Redirect(Url.BlogForAdmin(blog));
}
public ActionResult Edit(int id) {
public ActionResult Edit(int blogId) {
if (!Services.Authorizer.Authorize(Permissions.ManageBlogs, T("Not allowed to edit blog")))
return new HttpUnauthorizedResult();
var blog = _blogService.Get(id, VersionOptions.Latest);
var blog = _blogService.Get(blogId, VersionOptions.Latest);
if (blog == null)
return HttpNotFound();
@@ -95,11 +94,11 @@ namespace Orchard.Blogs.Controllers {
}
[HttpPost, ActionName("Edit")]
public ActionResult EditPOST(int id) {
public ActionResult EditPOST(int blogId) {
if (!Services.Authorizer.Authorize(Permissions.ManageBlogs, T("Couldn't edit blog")))
return new HttpUnauthorizedResult();
var blog = _blogService.Get(id, VersionOptions.DraftRequired);
var blog = _blogService.Get(blogId, VersionOptions.DraftRequired);
if (blog == null)
return HttpNotFound();
@@ -118,11 +117,11 @@ namespace Orchard.Blogs.Controllers {
}
[HttpPost]
public ActionResult Remove(int id) {
public ActionResult Remove(int blogId) {
if (!Services.Authorizer.Authorize(Permissions.ManageBlogs, T("Couldn't delete blog")))
return new HttpUnauthorizedResult();
var blog = _blogService.Get(id, VersionOptions.Latest);
var blog = _blogService.Get(blogId, VersionOptions.Latest);
if (blog == null)
return HttpNotFound();
@@ -148,8 +147,8 @@ namespace Orchard.Blogs.Controllers {
return View((object)viewModel);
}
public ActionResult Item(string blogSlug, Pager pager) {
BlogPart blogPart = _blogService.Get(blogSlug);
public ActionResult Item(int blogId, Pager pager) {
BlogPart blogPart = _blogService.Get(blogId, VersionOptions.Latest).As<BlogPart>();
if (blogPart == null)
return HttpNotFound();

View File

@@ -80,11 +80,11 @@ namespace Orchard.Blogs.Controllers {
//todo: the content shape template has extra bits that the core contents module does not (remove draft functionality)
//todo: - move this extra functionality there or somewhere else that's appropriate?
public ActionResult Edit(string blogSlug, int postId) {
public ActionResult Edit(int blogId, int postId) {
if (!Services.Authorizer.Authorize(Permissions.EditBlogPost, T("Couldn't edit blog post")))
return new HttpUnauthorizedResult();
var blog = _blogService.Get(blogSlug);
var blog = _blogService.Get(blogId, VersionOptions.Latest);
if (blog == null)
return HttpNotFound();
@@ -99,8 +99,8 @@ namespace Orchard.Blogs.Controllers {
[HttpPost, ActionName("Edit")]
[FormValueRequired("submit.Save")]
public ActionResult EditPOST(string blogSlug, int postId, string returnUrl) {
return EditPOST(blogSlug, postId, returnUrl, contentItem => {
public ActionResult EditPOST(int blogId, int postId, string returnUrl) {
return EditPOST(blogId, postId, returnUrl, contentItem => {
if (!contentItem.Has<IPublishingControlAspect>() && !contentItem.TypeDefinition.Settings.GetModel<ContentTypeSettings>().Draftable)
Services.ContentManager.Publish(contentItem);
});
@@ -108,15 +108,15 @@ namespace Orchard.Blogs.Controllers {
[HttpPost, ActionName("Edit")]
[FormValueRequired("submit.Publish")]
public ActionResult EditAndPublishPOST(string blogSlug, int postId, string returnUrl) {
return EditPOST(blogSlug, postId, returnUrl, contentItem => Services.ContentManager.Publish(contentItem));
public ActionResult EditAndPublishPOST(int blogId, int postId, string returnUrl) {
return EditPOST(blogId, postId, returnUrl, contentItem => Services.ContentManager.Publish(contentItem));
}
public ActionResult EditPOST(string blogSlug, int postId, string returnUrl, Action<ContentItem> conditionallyPublish) {
public ActionResult EditPOST(int blogId, int postId, string returnUrl, Action<ContentItem> conditionallyPublish) {
if (!Services.Authorizer.Authorize(Permissions.EditBlogPost, T("Couldn't edit blog post")))
return new HttpUnauthorizedResult();
var blog = _blogService.Get(blogSlug);
var blog = _blogService.Get(blogId, VersionOptions.Latest);
if (blog == null)
return HttpNotFound();
@@ -178,16 +178,16 @@ namespace Orchard.Blogs.Controllers {
ActionResult RedirectToEdit(IContent item) {
if (item == null || item.As<BlogPostPart>() == null)
return HttpNotFound();
return RedirectToAction("Edit", new { BlogSlug = item.As<IRoutableAspect>().Path, PostId = item.ContentItem.Id });
return RedirectToAction("Edit", new { BlogId = item.As<BlogPostPart>().BlogPart.Id, PostId = item.ContentItem.Id });
}
[ValidateAntiForgeryTokenOrchard]
public ActionResult Delete(string blogSlug, int postId) {
public ActionResult Delete(int blogId, int postId) {
//refactoring: test PublishBlogPost/PublishOthersBlogPost in addition if published
if (!Services.Authorizer.Authorize(Permissions.DeleteBlogPost, T("Couldn't delete blog post")))
return new HttpUnauthorizedResult();
var blog = _blogService.Get(blogSlug);
var blog = _blogService.Get(blogId, VersionOptions.Latest);
if (blog == null)
return HttpNotFound();
@@ -198,15 +198,15 @@ namespace Orchard.Blogs.Controllers {
_blogPostService.Delete(post);
Services.Notifier.Information(T("Blog post was successfully deleted"));
return Redirect(Url.BlogForAdmin(blog));
return Redirect(Url.BlogForAdmin(blog.As<BlogPart>()));
}
[ValidateAntiForgeryTokenOrchard]
public ActionResult Publish(string blogSlug, int postId) {
public ActionResult Publish(int blogId, int postId) {
if (!Services.Authorizer.Authorize(Permissions.PublishBlogPost, T("Couldn't publish blog post")))
return new HttpUnauthorizedResult();
var blog = _blogService.Get(blogSlug);
var blog = _blogService.Get(blogId, VersionOptions.Latest);
if (blog == null)
return HttpNotFound();
@@ -217,15 +217,15 @@ namespace Orchard.Blogs.Controllers {
_blogPostService.Publish(post);
Services.Notifier.Information(T("Blog post successfully published."));
return Redirect(Url.BlogForAdmin(blog));
return Redirect(Url.BlogForAdmin(blog.As<BlogPart>()));
}
[ValidateAntiForgeryTokenOrchard]
public ActionResult Unpublish(string blogSlug, int postId) {
public ActionResult Unpublish(int blogId, int postId) {
if (!Services.Authorizer.Authorize(Permissions.PublishBlogPost, T("Couldn't unpublish blog post")))
return new HttpUnauthorizedResult();
var blog = _blogService.Get(blogSlug);
var blog = _blogService.Get(blogId, VersionOptions.Latest);
if (blog == null)
return HttpNotFound();
@@ -236,7 +236,7 @@ namespace Orchard.Blogs.Controllers {
_blogPostService.Unpublish(post);
Services.Notifier.Information(T("Blog post successfully unpublished."));
return Redirect(Url.BlogForAdmin(blog));
return Redirect(Url.BlogForAdmin(blog.As<BlogPart>()));
}
bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) {

View File

@@ -40,23 +40,23 @@ namespace Orchard.Blogs.Extensions {
}
public static string BlogForAdmin(this UrlHelper urlHelper, BlogPart blogPart) {
return urlHelper.Action("Item", "BlogAdmin", new { blogSlug = blogPart.As<IRoutableAspect>().Path, area = "Orchard.Blogs" });
return urlHelper.Action("Item", "BlogAdmin", new { blogId = blogPart.Id, area = "Orchard.Blogs" });
}
public static string BlogCreate(this UrlHelper urlHelper) {
return urlHelper.Action("Create", "BlogAdmin", new {area = "Orchard.Blogs"});
return urlHelper.Action("Create", "BlogAdmin", new { area = "Orchard.Blogs" });
}
public static string BlogEdit(this UrlHelper urlHelper, BlogPart blogPart) {
return urlHelper.Action("Edit", "BlogAdmin", new { blogPart.Id, area = "Orchard.Blogs" });
return urlHelper.Action("Edit", "BlogAdmin", new { blogId = blogPart.Id, area = "Orchard.Blogs" });
}
public static string BlogRemove(this UrlHelper urlHelper, BlogPart blogPart) {
return urlHelper.Action("Remove", "BlogAdmin", new { blogPart.Id, area = "Orchard.Blogs" });
return urlHelper.Action("Remove", "BlogAdmin", new { blogId = blogPart.Id, area = "Orchard.Blogs" });
}
public static string BlogPostCreate(this UrlHelper urlHelper, BlogPart blogPart) {
return urlHelper.Action("Create", "BlogPostAdmin", new { blogSlug = blogPart.As<IRoutableAspect>().Path, area = "Orchard.Blogs" });
return urlHelper.Action("Create", "BlogPostAdmin", new { blogId = blogPart.Id, area = "Orchard.Blogs" });
}
public static string BlogPost(this UrlHelper urlHelper, BlogPostPart blogPostPart) {
@@ -64,19 +64,19 @@ namespace Orchard.Blogs.Extensions {
}
public static string BlogPostEdit(this UrlHelper urlHelper, BlogPostPart blogPostPart) {
return urlHelper.Action("Edit", "BlogPostAdmin", new { blogSlug = blogPostPart.BlogPart.As<IRoutableAspect>().Path, postId = blogPostPart.Id, area = "Orchard.Blogs" });
return urlHelper.Action("Edit", "BlogPostAdmin", new { blogId = blogPostPart.BlogPart.Id, postId = blogPostPart.Id, area = "Orchard.Blogs" });
}
public static string BlogPostDelete(this UrlHelper urlHelper, BlogPostPart blogPostPart) {
return urlHelper.Action("Delete", "BlogPostAdmin", new { blogSlug = blogPostPart.BlogPart.As<IRoutableAspect>().Path, postId = blogPostPart.Id, area = "Orchard.Blogs" });
return urlHelper.Action("Delete", "BlogPostAdmin", new { blogId = blogPostPart.BlogPart.Id, postId = blogPostPart.Id, area = "Orchard.Blogs" });
}
public static string BlogPostPublish(this UrlHelper urlHelper, BlogPostPart blogPostPart) {
return urlHelper.Action("Publish", "BlogPostAdmin", new { blogSlug = blogPostPart.BlogPart.As<IRoutableAspect>().Path, postId = blogPostPart.Id, area = "Orchard.Blogs" });
return urlHelper.Action("Publish", "BlogPostAdmin", new { blogId = blogPostPart.BlogPart.Id, postId = blogPostPart.Id, area = "Orchard.Blogs" });
}
public static string BlogPostUnpublish(this UrlHelper urlHelper, BlogPostPart blogPostPart) {
return urlHelper.Action("Unpublish", "BlogPostAdmin", new { blogSlug = blogPostPart.BlogPart.As<IRoutableAspect>().Path, postId = blogPostPart.Id, area = "Orchard.Blogs" });
return urlHelper.Action("Unpublish", "BlogPostAdmin", new { blogId = blogPostPart.BlogPart.Id, postId = blogPostPart.Id, area = "Orchard.Blogs" });
}
}
}

View File

@@ -65,13 +65,13 @@ namespace Orchard.Blogs.Handlers {
{"Area", "Orchard.Blogs"},
{"Controller", "BlogAdmin"},
{"Action", "Edit"},
{"Id", context.ContentItem.Id}
{"blogId", context.ContentItem.Id}
};
context.Metadata.RemoveRouteValues = new RouteValueDictionary {
{"Area", "Orchard.Blogs"},
{"Controller", "BlogAdmin"},
{"Action", "Remove"},
{"Id", context.ContentItem.Id}
{"blogId", context.ContentItem.Id}
};
}
}

View File

@@ -30,9 +30,10 @@ namespace Orchard.Blogs.Handlers {
OnUpdateEditorShape<BlogPostPart>(SetModelProperties);
OnInitializing<BlogPostPart>((context, bp) => {
var blogSlug = requestContext.RouteData.Values.ContainsKey("blogSlug") ? requestContext.RouteData.Values["blogSlug"] as string : null;
if (!string.IsNullOrEmpty(blogSlug)) {
bp.BlogPart = blogService.Get(blogSlug);
var blogId = requestContext.RouteData.Values.ContainsKey("blogId") ? requestContext.RouteData.Values["blogId"] as string : null;
if (!string.IsNullOrEmpty(blogId)) {
var blog = blogService.Get(int.Parse(blogId), VersionOptions.Latest);
bp.BlogPart = blog.As<BlogPart>();
return;
}
@@ -72,14 +73,14 @@ namespace Orchard.Blogs.Handlers {
{"Area", "Orchard.Blogs"},
{"Controller", "BlogPostAdmin"},
{"Action", "Create"},
{"blogSlug", blogPost.BlogPart.As<RoutePart>().Slug}
{"blogId", blogPost.BlogPart.Id}
};
context.Metadata.EditorRouteValues = new RouteValueDictionary {
{"Area", "Orchard.Blogs"},
{"Controller", "BlogPostAdmin"},
{"Action", "Edit"},
{"postId", context.ContentItem.Id},
{"blogSlug", blogPost.BlogPart.As<RoutePart>().Slug}
{"blogId", blogPost.BlogPart.Id}
};
context.Metadata.RemoveRouteValues = new RouteValueDictionary {
{"Area", "Orchard.Blogs"},

View File

@@ -35,7 +35,7 @@ namespace Orchard.Blogs {
},
new RouteDescriptor {
Route = new Route(
"Admin/Blogs/{id}/Edit",
"Admin/Blogs/{blogId}/Edit",
new RouteValueDictionary {
{"area", "Orchard.Blogs"},
{"controller", "BlogAdmin"},
@@ -49,7 +49,7 @@ namespace Orchard.Blogs {
},
new RouteDescriptor {
Route = new Route(
"Admin/Blogs/{id}/Remove",
"Admin/Blogs/{blogId}/Remove",
new RouteValueDictionary {
{"area", "Orchard.Blogs"},
{"controller", "BlogAdmin"},
@@ -63,15 +63,13 @@ namespace Orchard.Blogs {
},
new RouteDescriptor {
Route = new Route(
"Admin/Blogs/{blogSlug}",
"Admin/Blogs/{blogId}",
new RouteValueDictionary {
{"area", "Orchard.Blogs"},
{"controller", "BlogAdmin"},
{"action", "Item"}
},
new RouteValueDictionary {
{"blogSlug", _blogSlugConstraint}
},
new RouteValueDictionary (),
new RouteValueDictionary {
{"area", "Orchard.Blogs"}
},
@@ -79,15 +77,13 @@ namespace Orchard.Blogs {
},
new RouteDescriptor {
Route = new Route(
"Admin/Blogs/{blogSlug}/Posts/Create",
"Admin/Blogs/{blogId}/Posts/Create",
new RouteValueDictionary {
{"area", "Orchard.Blogs"},
{"controller", "BlogPostAdmin"},
{"action", "Create"}
},
new RouteValueDictionary {
{"blogSlug", _blogSlugConstraint}
},
new RouteValueDictionary (),
new RouteValueDictionary {
{"area", "Orchard.Blogs"}
},
@@ -95,15 +91,13 @@ namespace Orchard.Blogs {
},
new RouteDescriptor {
Route = new Route(
"Admin/Blogs/{blogSlug}/Posts/{postId}/Edit",
"Admin/Blogs/{blogId}/Posts/{postId}/Edit",
new RouteValueDictionary {
{"area", "Orchard.Blogs"},
{"controller", "BlogPostAdmin"},
{"action", "Edit"}
},
new RouteValueDictionary {
{"blogSlug", _blogSlugConstraint}
},
new RouteValueDictionary (),
new RouteValueDictionary {
{"area", "Orchard.Blogs"}
},
@@ -111,15 +105,13 @@ namespace Orchard.Blogs {
},
new RouteDescriptor {
Route = new Route(
"Admin/Blogs/{blogSlug}/Posts/{postId}/Delete",
"Admin/Blogs/{blogId}/Posts/{postId}/Delete",
new RouteValueDictionary {
{"area", "Orchard.Blogs"},
{"controller", "BlogPostAdmin"},
{"action", "Delete"}
},
new RouteValueDictionary {
{"blogSlug", _blogSlugConstraint}
},
new RouteValueDictionary (),
new RouteValueDictionary {
{"area", "Orchard.Blogs"}
},
@@ -127,15 +119,13 @@ namespace Orchard.Blogs {
},
new RouteDescriptor {
Route = new Route(
"Admin/Blogs/{blogSlug}/Posts/{postId}/Publish",
"Admin/Blogs/{blogId}/Posts/{postId}/Publish",
new RouteValueDictionary {
{"area", "Orchard.Blogs"},
{"controller", "BlogPostAdmin"},
{"action", "Publish"}
},
new RouteValueDictionary {
{"blogSlug", _blogSlugConstraint}
},
new RouteValueDictionary (),
new RouteValueDictionary {
{"area", "Orchard.Blogs"}
},
@@ -143,15 +133,13 @@ namespace Orchard.Blogs {
},
new RouteDescriptor {
Route = new Route(
"Admin/Blogs/{blogSlug}/Posts/{postId}/Unpublish",
"Admin/Blogs/{blogId}/Posts/{postId}/Unpublish",
new RouteValueDictionary {
{"area", "Orchard.Blogs"},
{"controller", "BlogPostAdmin"},
{"action", "Unpublish"}
},
new RouteValueDictionary {
{"blogSlug", _blogSlugConstraint}
},
new RouteValueDictionary (),
new RouteValueDictionary {
{"area", "Orchard.Blogs"}
},

View File

@@ -281,8 +281,9 @@ namespace Orchard.Setup.Services {
// create a welcome page that's promoted to the home page
var page = contentManager.Create("Page", VersionOptions.Draft);
page.As<RoutePart>().Title = T("Welcome to Orchard!").Text;
page.As<RoutePart>().Path = "";
page.As<RoutePart>().Slug = "";
page.As<RoutePart>().Path = "welcome-to-orchard";
page.As<RoutePart>().Slug = "welcome-to-orchard";
page.As<RoutePart>().PromoteToHomePage = true;
page.As<BodyPart>().Text = T(
@"<p>You've successfully setup your Orchard Site and this is the homepage of your new site.
Here are a few things you can look at to get familiar with the application.
@@ -304,7 +305,6 @@ Modules are created by other users of Orchard just like you so if you feel up to
<p>Thanks for using Orchard The Orchard Team </p>", page.Id).Text;
contentManager.Publish(page);
siteSettings.Record.HomePage = "RoutableHomePageProvider;" + page.Id;
// add a menu item for the shiny new home page
var menuItem = contentManager.Create("MenuItem");