Feedback
Did this article resolve your question/issue?

   

Article

MVC: Best practices when implementing custom widgets

« Go Back

Information

 
TitleMVC: Best practices when implementing custom widgets
URL NameBest-practices-when-implementing-custom-MVC-Widgets
Article Number000144145
EnvironmentProduct: Sitefinity
Versions: 10.x, 11.x, 12.x, 13.x, 14.x
OS: All supported OS versions
Database: All supported Microsoft SQL Server versions
Question/Problem Description

How to implement canonical URLs for a custom widget?
What are some best practices when developing custom MVC widgets?
How to reuse built-in MVC widgets logic in custom widgets?

Steps to Reproduce
Clarifying Information
Error Message
Defect Number
Enhancement Number
Cause
Resolution
When implementing custom widgets, consider implementing the following interfaces and/or an abstract base controller class:

ContentBaseController (abstract class)

Holds the main functionality for constructing all the out-of-the-box SEO properties for the widget when displaying a single item - Detail view mode. When this class is implemented, the InitializeMetadataDetailsViewBag method can be called - pass in it the item that is currently being displayed, and based on the item and the SEO configurations of the site, Sitefinity will automatically create OG and normal SEO properties.

ContentModelBase

This class holds the auxiliary implementation for various functionality - getting content locations, creating generic list view models, creating generic details view models, creating a list view model based on a related data item, part of the implementation of the IHasCacheDependency interface, etc.

IContentLocatableView

By implementing this interface, Sitefinity will know that a certain content type is displayed on a certain amount of pages, thus being able to determine priorities for canonical URLs:

IHasCacheDependency

The implementation of this interface will provide proper cache invalidation for the respective content type, in the custom widget, i.e. if a custom widget is displaying some items from a certain content type, and this interface is not implemented, there can be stale content displayed in the following scenario:
1. The user that is logged in is not a backend user (backend users do not have cache), or no user is logged in
2. The widget is displayed on a page, and the user has requested that page, thus the query made by the widget is cached according to the cache profile
3. An editor makes a change to an item that the widget is displaying
4. The user will not see the change until the cache expires since there is no way for Sitefinity to "know" that this item(s) are being displayed by this widget

IRouteMapper

This interface is used when one would like custom logic for resolving the route parameters. Implementing it will mean that the default behavior for MVC routing will not work but will depend only on the custom implementation. For example, there is a custom book widget (displays list of books) on a page book-catalogue. Custom routing logic can be implemented for filtering by a specific author that will resolve the desired author from the route, e.g. http://mysite/book-catalog/Index/Author

HandleUnknownAction

protected override void HandleUnknownAction(string actionName)
{
    this.ActionInvoker.InvokeAction(this.ControllerContext, "Index");
}

This method is the way for Sitefinity's widgets to retain a correct state in certain scenarios - one of these is the details mode of a separate widget. In short, when a page is rendered with a widget in a mode different than the default one, Sitefinity checks all the widgets on the page if they can go into that mode. In this particular case, the news widget is going into details mode and Sitefinity is trying to push all widgets in this mode. The widgets that have Details action will get mapped earlier and Sitefinity will know not to execute them, but the widgets that do not must implement the HandleUnknownAction method so that the developer can specify what action should be executed in such a case.

Returning a view 

When a view must be returned, always explicitly name the view that is going to be called.

return this.View("ViewName", viewModel);

For a full sample see the attached file to this article.

Workaround
Notes
Last Modified Date11/1/2021 9:06 AM
Attachment 
Files 1. Mvc.zip
Disclaimer The origins of the information on this site may be internal or external to Progress Software Corporation (“Progress”). Progress Software Corporation makes all reasonable efforts to verify this information. However, the information provided is for your information only. Progress Software Corporation makes no explicit or implied claims to the validity of this information.

Any sample code provided on this site is not supported under any Progress support program or service. The sample code is provided on an "AS IS" basis. Progress makes no warranties, express or implied, and disclaims all implied warranties including, without limitation, the implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample code is borne by the user. In no event shall Progress, its employees, or anyone else involved in the creation, production, or delivery of the code be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample code, even if Progress has been advised of the possibility of such damages.