Five New Optimizely Certifications are Here! Validate your expertise and advance your career with our latest certification exams. Click here to find out more
Five New Optimizely Certifications are Here! Validate your expertise and advance your career with our latest certification exams. Click here to find out more
I think in CMS 12 you have a simpler option available to you. In the configure method of your startup class you should have a call to endpoints.MapContent(). There's an extension method you can call on that called "MapTemplate" which allows you to register a custom route pattern for your controller which you could call like this:
app.UseEndpoints(endpoints =>
{
endpoints.MapContent()
.MapTemplate<BlogListPageController>("{page}");
});
You then shouldn't need the partial router at all.
Paul,
Prior to adding this issue, I tried and I got an error (see below)
app.UseEndpoints(endpoints =>
{
endpoints.MapContent()
.MapTemplate<BlogListPageController>("{action}/{page:int}");
});
then based on your reply, I just tried
app.UseEndpoints(endpoints =>
{
endpoints.MapContent()
.MapTemplate<BlogListPageController>("{page:int}");
endpoints.MapControllers();
endpoints.MapRazorPages();
}
and
app.UseEndpoints(endpoints =>
{
endpoints.MapContent()
.MapTemplate<BlogListPageController>("{page}");
endpoints.MapControllers();
endpoints.MapRazorPages();
}
But I'm getting this error when going to /blog/3 which is the same error I got when I included the action in the pattern
ArgumentNullException: Value cannot be null. (Parameter 'propertyName')
EPiServer.Framework.Validator.ThrowIfNullOrEmpty(string name, string value)
EPiServer.Web.Internal.BlobResolver.ResolveProperty(IContentData content, string propertyName)
EPiServer.Core.Routing.Internal.BlobPartialRouter.RoutePartial(IContent content, UrlResolverContext segmentContext)
EPiServer.Core.Routing.PartialRouter<TIncoming, TOutgoing>.RoutePartial(IContent content, UrlResolverContext urlResolverContext)
EPiServer.Core.Routing.Pipeline.Internal.PartialUrlResolverPipelineStep.Resolve(UrlResolverContext context, UrlResolverOptions options)
EPiServer.Core.Routing.Internal.DefaultContentUrlResolver.Resolve(Uri url, UrlResolverOptions options)
EPiServer.Web.Routing.Internal.PipelineUrlResolver.Route(UrlBuilder urlBuilder, RouteArguments routeArguments)
EPiServer.Web.Routing.Matching.Internal.ContentMatcherPolicy.MapEndpointAsync(Endpoint originalEndpoint, HttpContext httpContext)
EPiServer.Web.Routing.Matching.Internal.ContentMatcherPolicy.ApplyAsync(HttpContext httpContext, CandidateSet candidates)
Microsoft.AspNetCore.Routing.Matching.DfaMatcher.SelectEndpointWithPoliciesAsync(HttpContext httpContext, IEndpointSelectorPolicy[] policies, CandidateSet candidateSet)
Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.<Invoke>g__AwaitMatch|8_1(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task matchTask)
SixLabors.ImageSharp.Web.Middleware.ImageSharpMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Looking at that error, I wonder whether there's a route already registered which is being a bit over zealous in what it's handling. It looks like the request is being processed by SixLabors.ImageSharp.Web.Middleware.ImageSharpMiddleware which has presumably spotted the integer at the end of the URL and assumed it's an image dimension.
If it is that, maybe try changing the route pattern to something like this:
MapTemplate<BlogListPageController>("p{page:int}/")
then, rather than /blog/3 it would be /blog/p3
I disabled the SixeLabors code and I still get an error. I tried tried:
endpoints.MapContent()
.MapTemplate<BlogListPageController>("{action}/p{page:int}");
endpoints.MapContent()
.MapTemplate<BlogListPageController>("{action}/{page:int}");
endpoints.MapContent()
.MapTemplate<BlogListPageController>("{page:int}");
Error:
ArgumentNullException: Value cannot be null. (Parameter 'propertyName')
EPiServer.Framework.Validator.ThrowIfNullOrEmpty(string name, string value)
EPiServer.Web.Internal.BlobResolver.ResolveProperty(IContentData content, string propertyName)
EPiServer.Core.Routing.Internal.BlobPartialRouter.RoutePartial(IContent content, UrlResolverContext segmentContext)
EPiServer.Core.Routing.PartialRouter<TIncoming, TOutgoing>.RoutePartial(IContent content, UrlResolverContext urlResolverContext)
EPiServer.Core.Routing.Pipeline.Internal.PartialUrlResolverPipelineStep.Resolve(UrlResolverContext context, UrlResolverOptions options)
EPiServer.Core.Routing.Internal.DefaultContentUrlResolver.Resolve(Uri url, UrlResolverOptions options)
EPiServer.Web.Routing.Internal.PipelineUrlResolver.Route(UrlBuilder urlBuilder, RouteArguments routeArguments)
EPiServer.Web.Routing.Matching.Internal.ContentMatcherPolicy.MapEndpointAsync(Endpoint originalEndpoint, HttpContext httpContext)
EPiServer.Web.Routing.Matching.Internal.ContentMatcherPolicy.ApplyAsync(HttpContext httpContext, CandidateSet candidates)
Microsoft.AspNetCore.Routing.Matching.DfaMatcher.SelectEndpointWithPoliciesAsync(HttpContext httpContext, IEndpointSelectorPolicy[] policies, CandidateSet candidateSet)
Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.<Invoke>g__AwaitMatch|8_1(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task matchTask)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
NOTE: One of the reason I was going down the path of using IPartialRouter was so the Canonical URL will be correct and include the page number (not yet implemented in code).
I added a note, but to close this out, the issue was that page is a reserved word, so we used pageNum.
In Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseEndpoints(endpoints =>
{
endpoints.MapContent()
// pagination routing
.MapTemplate<BlogListPageController>("{pageNum:int}");
});
}
In BlogListPageController.cs:
public ActionResult Index(BlogListPage currentPage, int? pageNum = 1)
{
var model = new BlogListPageViewModel(currentPage);
return View(PopulateViewModel(model, pageNum ?? 1));
}
I have a page type of BlogListPage with a controller:
public class BlogListPageController : PageController<BlogListPage> { public ActionResult Index(BlogListPage currentPage, int? page = 1, BlogFiltersRequest filters = null) { var model = new BlogListPageViewModel(currentPage); return View(PopulateViewModel(model, page ?? 1, filters)); } }
I am able to call this page: /blog and /blog?page=3, but I want to be able to call page 3 using this url: /blog/3
I have implemented a Partial Route and registed it
public class BlogPaginationPartialRouting : IPartialRouter<BlogListPage, BlogListPage> { public object RoutePartial(BlogListPage content, UrlResolverContext urlResolverContext) { // get the next url segment, which should be the post url var continentPart = urlResolverContext.GetNextRemainingSegment(urlResolverContext.RemainingPath); // check if part is an int if (continentPart != null && int.TryParse(continentPart.Next, out int page)) { // add page parameter urlResolverContext.RouteValues.Add("page", (int?)page); // set the rest of the path urlResolverContext.RemainingPath = continentPart.Remaining; } // return the blog or null return content; } public PartialRouteData GetPartialVirtualPath(BlogListPage content, UrlGeneratorContext urlGeneratorContext) { return new PartialRouteData { BasePathRoot = content.ContentLink, PartialVirtualPath = string.Empty }; } }
When I run and set breakpoints and call /blog/3, BlogPaginationPartialRouting.RoutePartial() is hit and the page value is set. And the the controller Index action is hit, but the page parameter is set to the default of 1 and not to 3.
How can I set the action parameter for the page value in the IPartialRouter?
I found this https://world.optimizely.com/forum/developer-forum/CMS/Thread-Container/2017/3/custom-routing-with-extra-parameters-in-action-method post, but it is for older version.