One of my favorite features in SharePoint is the ability to edit a web part page in a draft version, design the page layout conveniently and then finally publish all the changes in a click. In ASP.NET web parts, this feature is not coming out of the box (changes are propagating immediately and can be seen by all users). For that reason, designers & content authors are unable to complete a page design before making the changes visible. In this article I will show how to extend the Web Parts framework and create a Publishing Control that will add Draft & Publish functionality.
To download the demo solution click here: Web Parts solution with publishing control.
This Publishing control facilitates authorized users to edit a page in a draft mode before publishing. The control dictates the following editing workflow: The user can not edit/design/add web parts in the page unless he moves to ‘Draft State’ by clicking on “Design” on the Publishing Control, then in Draft State he can now see the buttons “Design”, “Edit” and “catalog”. When the user make changes in the page they can’t be seen by the page visitors immediately, instead they are saved for the draft version. When the user is done with the page design, he clicks “Publish” and only then all the changes are being saved for the shared version and affect the page visitors.
The publishing control before entering “draft mode”:
The publishing control in “draft mode”:
You can examine and watch the publishing control by simply opening the solution, change the connection string in Web.Config and run the solution. To install the control in your solution, please follow the instructions below:
1. Download the attached solution.
2. Compile the solution and place the file: EShipping.webbinEShipping.portalElements.dll in the folder which contains your assemblies (e.g. your application ‘bin’ folder).
3. Replace the default PersonalizationProvider with the smarterPersonalizationProvider: Open Web.Config and make the ” webParts ” sectin look like the following:
<webParts enableExport="true" > <personalization defaultProvider="smarterPersonalizationProvider" > <providers> <remove name="AspNetSqlPersonalizationProvider"/> <add name="smarterPersonalizationProvider" type="EShipping.portalElements.smarterPersonalizationProvider, EShipping.portalElements" connectionStringName="LocalSqlServer"/> </providers> <authorization> <allow users="*" verbs="enterSharedScope"/> <allow users="*" verbs="modifyState"/> </authorization> </personalization> </webParts>
4. From the web project (EShipping.Web) copy the control “/controls/PanelWithPublishing.ascx” and place it somewhere in your solution.
5. In your web part page (or in the master page) declare and place the publishing panel control.
6. Open your web part page (you must be logged-in to design a page). You should now see the Publishing Panel and all the pre-described functionality should be available.
7. You probably want to change the look & feel of the Publishing Control to make it similar to your application design.
Behind the scenes
When an authorized user enters a page, the publishing panel control hides all the tool buttons (Edit/Design/Catalog) so basically nobody can edit the page. To edit the page a user must click “Design” then the control redirects the page to itself with an added query string parameter: http://www.someUrl.aspx?state=draft.
The WP framework saves all personalization state per URL, for each URL there will be different personalization record. When the page loads with state=draft in the URL, the control works on a draft state so it makes all the tool buttons visible – the user can now edit the page. All the changes are being saved in the context of the draft URL so there are actually two pages (personalization records): The main URL which is the version that all the visitors see (http://www.someUrl.aspx) and the draft URL which is available only to authorized users (http://www.someUrl.aspx?state=draft).
Fell free to change the URL formatting, for example to support REST URL (www.someURL.aspx/draft).
When the user clicks “Publish”, the control saves the entire web parts state into the origin URL (http://www.someUrl.aspx) – this cause the draft version layout to override the published version layout. This is how we “copy” the draft version changes into the published version.
The design problem
Out of the box, the WP framework operates as following: The WebPartManager is gathering all the web parts changes (i.e. personalization), packs them in an object named PersonalizationState and moves this package to the SqlPersonalizationProvider. The later, determines the current URL and saves the state where the URL is the identifier key. There are two main problems with this design:
1. The SqlPersonalizationProvider which supposed to be a persistent service, is dealing with the logic of the personalization and with HTTP issues (determination of the URL).
2. The SqlPersonalizationProvider API accepts the personalization data but not the URL (or some other abstract key which identify a personalization record) – actually it determines them by itself. No changes can be made (externally) to this important logic. For example, let’s say we want personalization which is based on the query string parameters (www.aSite.com?someKey=someValue , where different parameters will lead to different personalization data) – This can NOT be done using the API since the SqlPersonalizationProvider is always taking the raw URL and use it as the personalization key.
Basically, I needed a way to make the personalizationProvider saves personalization data with any URL given (to distinguish www.page.aspx from www.page.aspx?mode=draft) and to accept that parameter from the publishing control. I achieved this by overriding the SqlPersonalizationProvider and overriding the method ” LoadPersonalizationBlobs” and ” SavePersonalizationBlob” which are responsible for loading and saving personalization data. When overriding these methods we no longer take the raw URL from the http context but instead we fetch the class “webPartPersonalizationContext” from the http context dictionary – it is this class which is responsible to determine the URL.
“webPartPersonalizationContext” determines if we are in draft or published mode, it responsible for providing the URL for the personalization provider, it allows the publishing control to change the state by implementing the method: moveToPublished(). Because the PersonalizationProvider API does not contain a URL parameter, the “webPartPersonalizationContext” acts as a bridge between the publishing control and the personalization provider.