Tag: AJAX
API Routes stopped working in Sitecore 9

API Routes stopped working in Sitecore 9

We recently undertook a Sitecore 9 upgrade and one issue we encountered was controllers we had set up for API routes stopped working on content delivery servers. Content management and single server setups were unaffected.

Our routes had been set up by the controllers inheriting from SitecoreController and then using the default route which Sitecore would create. e.g. /api/sitecore/foo/getFoo

1public class FooController : SitecoreController
2{
3 ...
4 [System.Web.Http.HttpPost]
5 public ActionResult GetFoo(FooModel queryModel)
6 {
7 ...
8 }
9}

MVC.LegalRoutes

After a bit of investigation we found a new setting in Sitecore 9 called MVC.LegalRoutes. From the documentation:

MVC: Pipe separated list of route URL's that are not valid for use with Sitecore.Mvc.
For instance, the default ASP.NET route ({controller}/{action}/{id}) catches most requests that are actually meant to be handled by the default Sitecore route.
Default: "{controller}/{action}/{id}"

If we added our route to the list everything started to work ok.

1<setting name="Mvc.LegalRoutes" value="|Sitecore.Mvc:sitecore/shell/api/sitecore/{controller}/{action}|Sitecore.Mvc.Api:/api/sitecore/{controller}/{action}|" />

A different approach

Not wanting to just change random settings we stumble across we contacted Sitecore support to see what they thought.

The route 'api/sitecore/{controller}/{action}' is a pre-defined Sitecore SPEAK route for Sitecore Client usage. So when trying to access on a content delivery server where the Sitecore Client is disabled, it no longer works.

So to get around the issue we can start registering the route on content delivery servers through the Global.asax file.

1public override void Application_Start(object sender, EventArgs args)
2{
3 base.Application_Start(sender, args);
4
5 if (System.Configuration.ConfigurationManager.AppSettings["role:define"] == "ContentDelivery")
6 {
7 System.Web.Routing.RouteTable.Routes.MapRoute("Sitecore.Speak.Commands", "api/sitecore/{controller}/{action}");
8 }
9}

jQuery ajax

If you want to dynamically load content into your page chances are your going to need to do some sort of request to a server in the background. The simplest way of doing this is to use jQuery's ajax function.

Here's an example of a GET request and a POST request:

1$.ajax({
2 type: "POST", // Set the HTTP type to POST
3 url: "/serviceurl.aspx", // URL of the service
4 data: "{data: [{"Field1": 1}] }", // Data to be posted to the service
5 processData: false, // If set to true the data will be processed and turned into a query string
6 success: (function (result) {
7 // Result of the service call
8 }),
9 error: (function (jqXHR, textStatus, errorThrown) {
10 alert(errorThrown);
11 })
12});
13
14$.ajax({
15 type: "GET", // Set the HTTP type to GET
16 url: "/serviceurl.aspx", // URL of the service
17 dataType: "jsonp", // Set what type of data you want back
18 success: (function (result) {
19 // Function that will be called on success
20 })
21});

Introduction to Real Time Search

The Real Time Search this article relates to is a dll that comes included in the Web Client Software Factory one of Microsoft's patterns and practices downloads. What it can do and what this example will demonstrate is adding the ability cause a post back that will refresh an update panel as the users types into a search box. This can give a great effect and make a web app really user friendly in scenarios like searching photo's, emails or any general list.

First late me state though that this is in no way the most optimal way program. In most scenarios you could built a better result using something like JSON as their will be a lot less data transfer, which is also a general reason to avoid update panels. However this is also very quick and very easy to implement, not to mention if you've ever used update panels before you already know 90% of what's needed. This can also only work in situations where you have a good search that is going to return the result quickly, rather than leaving the user sitting there trying to work out why nothing's happening and where the search button has gone.

Implementing Real Time Search

For this example I will be filtering a table from a DB based on the search criteria and refreshing a Grid View with the results. I will be using a normal C# Web Site project with the Adventure Works sample DB from Microsoft. DB connection will be done using LINQ to EntityFramework, however there is no need to use this it is just my preference for the example.

First off set up you're website and db and make sure both are working with no problems. As results will be displayed in an Update Panel, get one of these along with a script manager added to your page, so it looks something like this:

1<form id="form1" runat="server">
2<div>
3<asp:ScriptManager ID="ScriptManager1" runat="server">
4</asp:ScriptManager>
5<asp:UpdatePanel ID="UpdatePanel1" runat="server">
6<ContentTemplate></ContentTemplate>
7</asp:UpdatePanel>
8</div>
9</form>

Next let's get the search working in the normal method, so I'm going to create my Entity Model and add a textbox and gridview to show the results. Again you can connect and show your results however you want. You should now have something like this in your aspx file:

1<form id="form1" runat="server">
2<div>
3<asp:ScriptManager ID="ScriptManager1" runat="server">
4</asp:ScriptManager>
5
6<asp:TextBox ID="txtSearch" runat="server" OnTextChanged="TextChanged" Text=""></asp:TextBox>
7
8<asp:UpdatePanel ID="UpdatePanel1" runat="server">
9<ContentTemplate>
10
11<asp:LinqDataSource ID="LinqDataSource1" runat="server" onselecting="LinqDataSource1_Selecting">
12</asp:LinqDataSource>
13
14<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataSourceID="LinqDataSource1">
15<Columns>
16<asp:BoundField DataField="ProductID" HeaderText="ProductID"
17ReadOnly="True" SortExpression="ProductID" />
18<asp:BoundField DataField="Name" HeaderText="Name"
19ReadOnly="True" SortExpression="Name" />
20<asp:BoundField DataField="ProductNumber" HeaderText="ProductNumber" ReadOnly="True" SortExpression="ProductNumber" />
21<asp:BoundField DataField="Color" HeaderText="Color"
22ReadOnly="True" SortExpression="Color" />
23<asp:BoundField DataField="SafetyStockLevel" HeaderText="SafetyStockLevel"
24ReadOnly="True" SortExpression="SafetyStockLevel" />
25<asp:BoundField DataField="ReorderPoint" HeaderText="ReorderPoint"
26ReadOnly="True" SortExpression="ReorderPoint" />
27</Columns>
28</asp:GridView>
29</ContentTemplate>
30
31</asp:UpdatePanel>
32</div>
33</form>

And this in your code behind:

1protected void LinqDataSource1_Selecting(object sender, LinqDataSourceSelectEventArgs e)
2{
3Model.AdventureWorks2008Entities AdventureWorkds = new Model.AdventureWorks2008Entities();
4var products = from p in AdventureWorkds.Product
5where p.Name.Contains(txtSearch.Text)
6select p;
7e.Result = products;
8}

Next its time to add the Real Time Search. Make sure you have the dll downloaded (you may need to compile the download to get it) and add it to your bin folder. Add the following to your page in the relevant places:

1<%@ Register Assembly="RealTimeSearch" Namespace="RealTimeSearch" TagPrefix="cc1" %>
2
3<cc1:RealTimeSearchMonitor ID="RealTimeSearchMonitor1" runat="server" AssociatedUpdatePanelID="UpdatePanel1">
4<ControlsToMonitor>
5<cc1:ControlMonitorParameter EventName="TextChanged" TargetID="txtSearch" />
6</ControlsToMonitor>
7</cc1:RealTimeSearchMonitor>

Important things to notice here are the AssociatedUpdatePanelId which tells the control what it has to refresh and the controls to monitor section which sets what the control to watch is called and the event name that will be fired when the post back is created. You will now need to corresponding control in your code behind like so:

1protected void TextChanged(object sender, EventArgs e)
2{
3GridView1.DataBind();
4}

Run the site and  you should find that the grid view now updates as you type (all be it with a slight delay).

To improve you can add other controls like update progress to show something happening which will help with any delays in displaying the results.

Refreshing an UpdatePanel using JavaScript

Update panels provide a really easy way to add partial page refresh's to a web page in order to give a more user friendly experience that's more like a common desktop App. By default a button within that update panel will cause a post back and so long as no elements outside of the update panel are affected in the post back only it will refresh. However what do you do if you need to refresh a panel based on something outside of the update panel. One option is just to place a hidden button within the Update Panel and trigger that to post back, however this appears a little hacky. The solution is to use JavaScript and the _doPostBack method.

The following bit of code is all you really need:

1<script type="text/javascript">
2function refreshUpdatePannel(param) {
3var prm = Sys.WebForms.PageRequestManager.getInstance();
4prm._doPostBack('<%=UpdatePanel1.ClientID %>', param);
5prm.add_endRequest(refreshUpdatePanelComplete);
6}
7
8
9function refreshUpdatePanelComplete() {
10//add code here for when the update pannel is refreshed
11}
12</script>

All you need to add is something to call the function.

The second parameter is optional and provides a way of passing some information back to the server to let it know what has just happened on the page.

When the update panel has finished refreshing the refreshUpdatePanelComplete function will fire so more JavaScript can be added if needed e.g. To reset whatever just happened on the page that caused the initial postback.