Skip to main content

A DD4T.net Implementation - Pagination (Models)

This post adds pagination capabilities to an AJAX Dynamic Component Presentation (DCP) presented in an earlier post Rendering Only a Partial for AJAX.

The use case is the following: we have a controller that displays a list of DCPs in a paginated way. Due to the dynamic nature of this list, the list of DCPs should be rendered as a partial using AJAX. This implies that an actual AJAX request is made to the controller, which triggers only the list of DCPs be recalculated and served back. The paginated AJAX URL produces HTML directly, therefore the Javascript logic can simply replace the container div 'innerHTML' with the markup received from pagination. A whole page reload is thus not necessary. Additionally the list of DCPs can be sorted before being paginated, but about that in a later post.

This post descries the server-side model builder that generated the paginated list of models. In a subsequent post I will present the server-side views that render the same and the client-side Javascript performing the AJAX handling.

In RouteConfig.cs, I modified the partial AJAX route, by adding the lines highlighted below. They basically accept a new URL path parameter 'page' of type integer:

    routes.MapRoute(
        name: "DCP",
        url: "{controller}/{action}/{publication}/{component}/{view}/{partial}/{page}.ajax",
        defaults: new
        {
            partial = UrlParameter.Optional,
            page = UrlParameter.Optional
        },
        constraints: new
        {
            controller = @"\w{1,50}",
            action = @"\w{1,50}",
            publication = @"\d{1,3}",
            component = @"\d{1,6}",
            view = @"\w{1,50}",
            partial = @"_[\w\.]{1,50}",
            page = @"\d{0,3}"
        }
    );

An example URL for calling this partial paginated URL would be:

http://my.server.com/Device/Index/123/456/FullDetail/_PartialDocument/2.ajax

The URL above calls controller Device, action method Index on Component with id tcm:123-456 and uses view FullDetail to retrieve the DD4T strong model. The controller dispatches to view _PartialDocument.cshtml and displays page 2 of the list of documents that the controller built.

The models that make all this possible and their builders are as follows. We start with a very simple BaseBuilder class that holds a generic type 'ModelType' representing the actual model we build from. The class is abstract because it simply defines the signature of a Build() method to be implemented in one of the specific subclasses of it:

public abstract class BuilderBase<ModelType>
{
    public ModelType Model { get; protected set; }

    public BuilderBase(ModelType model)
    {
        Model = model;
    }

    public abstract void Build();
}

Next, we define base class AjaxBase, another generic that provides some common properties for AJAX calls, such as controller name, view, partialView, partialUrl:

public abstract class AjaxBase<ModelType>
    : BuilderBase<ModelType> where ModelType : ModelBase
{
    public string Controller { get; private set; }
    public bool IsPostback { get; protected set; }
    public string View { get; private set; }
    public string PartialView { get; private set; }
    public string PartialUrl
    {
        get
        {
            Uri uri = HttpContext.Current.Request.Url;
            TcmUri modelId = new TcmUri(Model.Id);

            return string.Format("http://{0}{1}/{2}/Index/{3}/{4}/{5}/{6}",
                uri.Host,
                uri.Port == 80 ? string.Empty : string.Format(":{0}", uri.Port),
                Controller,
                modelId.PublicationId,
                modelId.ItemId,
                View,
                PartialView);
        }
    }

    public AjaxBase(ModelType model, string controller,
        string view, string partialView)
        : base(model)
    {
        Controller = controller;
        View = view;
        PartialView = partialView;
    }
}

Next, we define the PaginationBase class. In order not to reinvent the wheel, this class makes use of the package PagedList, which "makes it easier for .Net developers to write paging code", and "allows you to take any IEnumerable(T) and by specifying the page size and desired page index, select only a subset of that list". The class defines properties such as PageNumber and PageSize that will be used later on to determine which sub-set of the paged list to display. The class also helps us construct the paged partial URL that we call from client-side Javascript.

public abstract class PaginationBase<ModelType, PagedType> : AjaxBase<ModelType>
    where ModelType : ModelBase
    where PagedType : ModelBase
{
    public IPagedList<PagedType> PagedItems { get; protected set; }
    public int PageNumber { get; set; }
    public int PageSize { get { return 10; } } // just an example

    public PaginationBase(ModelType model, string controller, string view,
        string partialView, int pageNumber)
        : base(model, controller, view, partialView)
    {
        PageNumber = Math.Max(1, pageNumber);
    }

    public string GetPagerUrl(int pageNumber)
    {
        return string.Format("{0}/{1}.ajax", PartialUrl, pageNumber);
    }

    protected void ToPagedList(IEnumerable<PagedType> items)
    {
        PagedItems = items.ToPagedList(PageNumber, PageSize);
    }
}

Next, the actual implementation of the paginated model is class DocumentPartial. This class defines the Build() method that creates the list of Document object and then paginates it. Notice the use of IsPostback property -- this implies the method Build is only invoked during a postback from AJAX, otherwise this model partial does not actually build the paginated list.

public class DocumentPartial : PaginationBase<Device, Document>
{
    public DocumentPartial(Device device, int pageNumber)
        : base(device, "Device", "Device", "_PartialDocument", pageNumber)
    { }

    public override void Build()
    {
        IsPostback = true;
        IEnumerable<Document> documents = // fetch the documents...
        ToPagedList(documents);
    }
}

Finally, the calling code sits inside the controller. In our case, the Device controller, action Index has the following simplified logic. The DocumentPartial class is used to build the list of Document only if the partial variable is actually specified and has value '_PartialDocument'; otherwise, the DocumentPartial is created, but not built. The controller either dispatches directly to the _PartialDocument partial view with the corresponding paginated list of Document, or it attaches the empty DocumentPartial to the ViewBag for later processing during the Device view.

public class DeviceController
{
    public ActionResult Index(int? publication, int? component,
            string view, string partial, int? page)
    {
        Device device;
        string viewName;
        ResolveModelAndView<Device>(publication, component, view, out device,
                out viewName);

        if (partial == null || partial == "_PartialDocument")
        {
            var documentPartial = new DocumentPartial(device, page ?? 1);
            if (partial == null)
            {
                ViewBag.DocumentPartial = documentPartial;
            }
            else
            {
                documentPartial.Build();
                return View(partial, documentPartial);
            }
        }

        return View(viewName, device);
    }
}

The mechanism above is quite flexible and can be used in many situations time and time again, by simply providing implementation classes for the abstract PaginationBase and wiring them up in the Component Controller.

The pagination, ajax, or base models are quite versatile and can be used together or individually depending on the requirement to display simple embedded lists of DCPs, or AJAX lists, or paginated AJAX lists, or any combination thereof.

In a follow-up post Pagination (Views), I tackled the server-side view logic.


Comments

Popular posts from this blog

Running sp_updatestats on AWS RDS database

Part of the maintenance tasks that I perform on a MSSQL Content Manager database is to run stored procedure sp_updatestats . exec sp_updatestats However, that is not supported on an AWS RDS instance. The error message below indicates that only the sa  account can perform this: Msg 15247 , Level 16 , State 1 , Procedure sp_updatestats, Line 15 [Batch Start Line 0 ] User does not have permission to perform this action. Instead there are several posts that suggest using UPDATE STATISTICS instead: https://dba.stackexchange.com/questions/145982/sp-updatestats-vs-update-statistics I stumbled upon the following post from 2008 (!!!), https://social.msdn.microsoft.com/Forums/sqlserver/en-US/186e3db0-fe37-4c31-b017-8e7c24d19697/spupdatestats-fails-to-run-with-permission-error-under-dbopriveleged-user , which describes a way to wrap the call to sp_updatestats and execute it under a different user: create procedure dbo.sp_updstats with execute as 'dbo' as

Scaling Policies

This post is part of a bigger topic Autoscaling Publishers in AWS . In a previous post we talked about the Auto Scaling Groups , but we didn't go into details on the Scaling Policies. This is the purpose of this blog post. As defined earlier, the Scaling Policies define the rules according to which the group size is increased or decreased. These rules are based on instance metrics (e.g. CPU), CloudWatch custom metrics, or even CloudWatch alarms and their states and values. We defined a Scaling Policy with Steps, called 'increase_group_size', which is triggered first by the CloudWatch Alarm 'Publish_Alarm' defined earlier. Also depending on the size of the monitored CloudWatch custom metric 'Waiting for Publish', the Scaling Policy with Steps can add a difference number of instances to the group. The scaling policy sets the number of instances in group to 1 if there are between 1000 and 2000 items Waiting for Publish in the queue. It also sets the

I Have Gone Dark

Maybe it's the Holidays, but my mood has gone pretty dark. That is, regarding the look and feel of my computer and Tridion CME, of course. What I did was to dim the lights on the operating system, so I installed Placebo themes for Windows 7 . I went for the Ashtray look -- great name :) My VM looks now like this: But, once you change the theme on Windows, you should 'match' the theme of your applications. Some skin easily, some not. The Office suite has an in-built scheme, which can be set to Black , but it doesn't actually dim the ribbon tool bars -- it looks quite weird. Yahoo Messenger is skinnable, but you can't change the big white panels where you actually 'chat'. Skype is not skinnable at all. For Chrome, there are plenty of grey themes. Now i'm using Pro Grey . But then I got into changing the theme of websites. While very few offer skinnable interfaces (as GMail does), I had to find a way to darken the websites... Enter Stylish -- a pl