I have a Razor script that creates a RSS feed on our page headers if there is an emergency. This feed needs to automatically refresh the area of the page without the user having to reload the page. How could I accomplish this? Here is my Razor:
@using System.Xml.XPath;
@using System.Xml;
@using umbraco.MacroEngines;
@inherits umbraco.MacroEngines.DynamicNodeContext
<header>
@{
//Fetch RSS XML
XmlTextReader remoteRSS = new XmlTextReader("http://www.getrave.com/rss/rose-hulman/channel1");
//Create new XML document
XmlDocument doc = new XmlDocument();
//Load in our remote XML into our XML document
doc.Load(remoteRSS);
//Select our nodes we want with some xPath
XmlNodeList rssItems = doc.SelectNodes("//item");
}
<div id="emergencyRss">
<ul class="rss-feed">
@{ //For each item node we can then ouput what we want
var i = 0;
foreach (XmlNode node in rssItems)
{
if(!@node["title"].InnerText.ToLower().Contains("all clear")) {
<li>
<a href="@node["link"].InnerText">@node["title"].InnerText</a>
<p class="description">@Html.Raw(@node["description"].InnerText)</p>
<div class="date">@Html.Raw(@node["pubDate"].InnerText)</div>
</li>
} else
{
}
}
}
</ul>
SignalR is pretty awesome. You could probably also go for some lower tech solutions if you wanted. Is the issue that you don't want the user to manually reload the page? Or that the page can't reload at all.
If it's the former case, then you could do something like have a line of javascript with a setTimeout that causes a page refresh every so many minutes. That's fairly quick to code and the only catch is the whole page reloading every so often (but at least it's doing it on its own).
Alternatively, you could alter your page so it's being rendered by a front-end framework like AngularJS or ReactJS, have Umbraco serve up the appropriate data via an API controller (instead of Razor), and then have the JS framework handle the updates asynchronously with an API service and two-way binding on the markup.
I guess I could go low tech if I could tell it to only do the automatic page load if the test for the xml feed title passes.
EDIT:
I tried this, and saw an issue. If I do this using location.reload(true); it would have to be constantly refreshing the page, which I don't think is a valid solution. I could have the reload only trigger after the original test is "true", but then there is the problem with the original test being displayed (unless someone goes to a different page, causing the message to appear. Doesn't appear that there is an easy solution to this.
A really simple solution could be to use this meta tag <meta http-equiv="refresh" content="30"> - The interval is set in seconds so you can change it to for instance 60 for making the page refresh itself every minute for instance. No javascript required :)
Could this help for a simple low tech solution without too much hassle?
I wish I could use this, but this code is for an emergy alert system and would be on the header of every page, even forms. Which, as you know would not work at all. Thanks for all your ideas though.
What about putting a JS Framework-powered section into only the RSS feed part of the page, while leaving the rest to the normal Razor view? Then you could have that tied to a controller that periodically pings an Umbraco API controller checking for new RSS content. When one or more items are returned from the API, it updates the feed's markup without reloading the whole page? That should meet your use case.
I can try to offer a more detailed explanation if that sounds like a workable solution.
The AngularJS "view" is independent of any concerns about whether you're using webforms or MVC. I'll write up some example code to provide a possible solution that might work. (Other frameworks could work as well, but I'm less familiar with those). I should get it up sometime later today.
Sorry it took a couple days. Life's been busy. Here's a solution that I think will do what you want, utilizing Umbraco's API wrapper and AngularJS.
If you don't know much about AngularJS, and want to get more familiar with it, I'm going to recommend this following tutorial that I think hits all the relevant points really well for someone starting out with it, by Dan Whalin. You can either watch his YouTube video "AngularJS in 60ish Minutes" (https://www.youtube.com/watch?v=i9MHigUZKEM) or check out his eBook version (http://weblogs.asp.net/dwahlin/angularjs-in-60-ish-minutes-the-ebook). There's a lot of good tutorials and documentation on AngularJS throughout the web, but those are great starts.
First you'll want a convenient C# model
// RssItemModel.cs
namespace YourWebsite.Web.Models.Resources
{
using Umbraco.Web.Media;
public class RssItem
{
public string Title { get; set; }
public string Link { get; set; }
public string Description { get; set; }
public string PublishDate { get; set; }
}
}
This model will then be used inside this Umbraco API controller that is going to be doing the retrieval of the RSS from the feed.
// RssApiController.cs
namespace YourWebsite.Web.Controllers.Api
{
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using YourWebsite.Web.Models;
using Umbraco.Web.WebApi;
public class RssApiController : UmbracoApiController
{
private const string RssFeedUrl = "http://www.getrave.com/rss/rose-hulman/channel1";
public IEnumerable<RssItem> GetRssItems()
{
var remoteRss = new XmlTextReader(RssFeedUrl);
var doc = new XmlDocument();
doc.Load(remoteRss);
XmlNodeList rssItems = doc.SelectNodes("//item");
return (from XmlNode node in rssItems where !node["title"].InnerText.ToLower().Contains("all clear") select new RssItem { Title = node["title"].InnerText, Link = node["link"].InnerText, Description = node["description"].InnerText, PublishDate = node["pubDate"].InnerText }).ToList();
}
}
}
Inside your Umbraco View/Template, you're going to want to replace your original Razor code for the feed with the following markup. This will connect to the AngularJS code to follow.
// Your Umbraco View
// Add this markup to your Umbraco view, replacing your original Razor code. This will connect to the Angular code.
<div id="emergencyRss" data-ng-app="rssApp" data-ng-controller="RssController">
<ul class="rss-feed">
<li data-ng-repeat="rssItem in rssItems">
<a href="{{rssItem.link}}">{{rssItem.title}}</a>
<p class="description">{{rssItem.description}}</p>
<div class="date">{{rssItem.publishDate}}</div>
</li>
</ul>
Add this to the end of your View's markup, just before the closing tag.
<!-- you'll need Angular, v1.3+ should be sufficient -->
<script src="~/scripts/angular.min.js"></script>
<!-- The following are scripts I show you below -->
<script src="~/scripts/ng.app.js"></script>
<script src="~/scripts/rss.api.service.js"></script>
<script src="~/scripts/rss.controller.js"></script>
I've broken the AngularJS code into three files to separate out code responsibility. The first is the "app", which creates the Angular app and has a couple convenient functions in it.
//ng.app.js
// Register the AngularJS App
(function () {
angular.module('rssApp', []);
}());
// Some JS app namespacing.
(function (rssApp, undefined) {
rssApp.Models = {};
rssApp.Tools = {};
}(window.rssApp = window.rssApp || {}));
// Tools for some basic functionality help.
(function (tools, undefined) {
/**
* @ngdoc method
* @name downCaseProperties
* @function
*
* param {object} object - A JSON object.
* returns {object}
* @description - Downcases all the property names of a JSON object.
*/
tools.downCaseProperties = function (object) {
var newObject = {};
for (var prop in object) {
if (object.hasOwnProperty(prop)) {
var propertyName = prop;
var propertyValue = object[prop];
var newPropertyName = propertyName.charAt(0).toLowerCase() + propertyName.slice(1);
if ((typeof propertyValue) == "object") {
propertyValue = rssApp.Tools.downCaseProperties(propertyValue);
}
newObject[newPropertyName] = propertyValue;
}
};
return newObject;
};
}(window.rssApp.Tools = window.rssApp.Tools || {}));
// Models for use with the API.
(function (models, undefined) {
/**
* @ngdoc method
* @name downCaseProperties
* @function
*
* param {object} data - A JSON object that may or may not contain properties following the RssItem model.
* returns {object} - Has the properties declared in this model, either blank or provided by the data parameter above.
* @description - Explicitly takes the incoming object and maps it to a new object with only those properties that match those in this RssItem model.
*/
models.RssItem = function (data) {
var self = this;
if (data == undefined) {
self.title = '';
self.link = '';
self.description = '';
self.publishDate = '';
} else {
self.title = data.title;
self.link = data.link;
self.description = data.description;
self.publishDate = data.publishDate;
}
};
}(window.rssApp.Models = window.rssApp.Models || {}));
This is the Angular service that will make calls to get info from the Umbraco API controller we built, via AJAX.
// rss.api.service.js
(function (app) {
var rssApiService = function ($http) {
var rssApiFactory = {};
/**
* @ngdoc method
* @name getRssItems
* @function
*
* @returns {array of RssModel}
* @description - Acquire a list of RSS Items from the RSS API, if any exist.
*/
rssApiFactory.getRssItems = function () {
return $http.get('/umbraco/api/RssApi/GetRssItems').then(function (response) {
if (response.data) {
var rssItems = [];
for (var i = 0; i < response.data.length; i++) {
rssItems.push(new rssApp.Models.RssItem(rssApp.Tools.downcaseProperties(rss.data[i]);
}
}
return rssItems;
});
};
return rssApiFactory;
};
app.factory('rssApiService', rssApiService);
}(angular.module('rssApiService')));
Lastly, we have the AngularJS controller. This will bind the RSS feed we're getting from the service we built to the HTML view. It has a function that's going to call your RSS feed every 30 seconds. Obviously change that to whatever makes sense.
//rss.controller.js
(function (app) {
var rssController = function ($scope, $interval, rssApiService) {
/*-------------------------------------------------------------------
* Initialization Methods
* ------------------------------------------------------------------*/
/**
* @ngdoc method
* @name init
* @function
*
* @description - Called when the $scope is initalized.
*/
$scope.init = function () {
$scope.setVariables();
$scope.activateRefreshInterval();
};
/**
* @ngdoc method
* @name activateRefreshInterval
* @function
*
* @description - Sets up an interval call to the updateRssFeed() member function every 30 seconds.
*/
$scope.activateRefreshInterval = function() {
$interval (function(){ $scope.updateRssFeed(); }, 30000); // updates every 30 seconds. Change to meet your applicaton's needs.
};
/**
* @ngdoc method
* @name setVariables
* @function
*
* @description - Sets the initial states of the $scope variables.
*/
$scope.setVariables = function () {
$scope.rssItems = [];
};
/**
* @ngdoc method
* @name updateRssFeed
* @function
*
* @description - Request a list of RSS items from the RSS API service, and assign them to $scope.rssItems.
*/
$scope.updateRssFeed = function() {
var promise = rssApiService.getRssItems();
promise.then(function(rssItems) {
$scope.rssItems = rssItems;
});
};
// call $scope.init() to initialize this controller.
$scope.init();
};
app.controller('RssController', ['$scope', '$interval', 'rssApiService', rssController]);
}(angular.module('rssApp')));
It's a lot of code to do what you want, but I'm pretty positive this could manage what you're looking to accomplish. Let me know if it helps, or if you have any questions!
@Jan - Thanks! And hey, great link. I'm going to keep that on hand for people in the future. Looks like it has some practical examples, which are always the best.
Refreshing RSS Feed Without Page Reload
I have a Razor script that creates a RSS feed on our page headers if there is an emergency. This feed needs to automatically refresh the area of the page without the user having to reload the page. How could I accomplish this? Here is my Razor:
Steve,
Only thing I can think of is using signalr, so create a hub and when your rss feed updates that gets hub to update all connected clients instantly. There are some umbraco projects already that use signalr see https://our.umbraco.org/projects/tag/SignalR you could look at source code and for more information on signalr see http://www.asp.net/signalr/overview/getting-started/tutorial-server-broadcast-with-signalr
Regards
Ismail
Steve,
SignalR is pretty awesome. You could probably also go for some lower tech solutions if you wanted. Is the issue that you don't want the user to manually reload the page? Or that the page can't reload at all.
If it's the former case, then you could do something like have a line of javascript with a setTimeout that causes a page refresh every so many minutes. That's fairly quick to code and the only catch is the whole page reloading every so often (but at least it's doing it on its own).
Alternatively, you could alter your page so it's being rendered by a front-end framework like AngularJS or ReactJS, have Umbraco serve up the appropriate data via an API controller (instead of Razor), and then have the JS framework handle the updates asynchronously with an API service and two-way binding on the markup.
Hope this helps with some initial ideas!
-Kyle
I guess I could go low tech if I could tell it to only do the automatic page load if the test for the xml feed title passes.
EDIT:
I tried this, and saw an issue. If I do this using location.reload(true); it would have to be constantly refreshing the page, which I don't think is a valid solution. I could have the reload only trigger after the original test is "true", but then there is the problem with the original test being displayed (unless someone goes to a different page, causing the message to appear. Doesn't appear that there is an easy solution to this.
Hi Steve
A really simple solution could be to use this meta tag
<meta http-equiv="refresh" content="30">
- The interval is set in seconds so you can change it to for instance 60 for making the page refresh itself every minute for instance. No javascript required :)Could this help for a simple low tech solution without too much hassle?
/Jan
Jan,
I knew I'd forgotten something even simpler. Yeah, if you're looking for a super easy option, this is the best I can think of.
-Kyle
I wish I could use this, but this code is for an emergy alert system and would be on the header of every page, even forms. Which, as you know would not work at all. Thanks for all your ideas though.
Steve,
What about putting a JS Framework-powered section into only the RSS feed part of the page, while leaving the rest to the normal Razor view? Then you could have that tied to a controller that periodically pings an Umbraco API controller checking for new RSS content. When one or more items are returned from the API, it updates the feed's markup without reloading the whole page? That should meet your use case.
I can try to offer a more detailed explanation if that sounds like a workable solution.
-Kyle
That sounds intriguing Kyle, but I don't know how that would work if we are using webforms not MVC.
Steve,
The AngularJS "view" is independent of any concerns about whether you're using webforms or MVC. I'll write up some example code to provide a possible solution that might work. (Other frameworks could work as well, but I'm less familiar with those). I should get it up sometime later today.
I wasn't able to carve out time today for writing up that example, but I haven't forgotten it.
Thanks Kyle! I appreaciate it!
Steve,
Sorry it took a couple days. Life's been busy. Here's a solution that I think will do what you want, utilizing Umbraco's API wrapper and AngularJS.
If you don't know much about AngularJS, and want to get more familiar with it, I'm going to recommend this following tutorial that I think hits all the relevant points really well for someone starting out with it, by Dan Whalin. You can either watch his YouTube video "AngularJS in 60ish Minutes" (https://www.youtube.com/watch?v=i9MHigUZKEM) or check out his eBook version (http://weblogs.asp.net/dwahlin/angularjs-in-60-ish-minutes-the-ebook). There's a lot of good tutorials and documentation on AngularJS throughout the web, but those are great starts.
First you'll want a convenient C# model
This model will then be used inside this Umbraco API controller that is going to be doing the retrieval of the RSS from the feed.
Inside your Umbraco View/Template, you're going to want to replace your original Razor code for the feed with the following markup. This will connect to the AngularJS code to follow.
Add this to the end of your View's markup, just before the closing tag.
I've broken the AngularJS code into three files to separate out code responsibility. The first is the "app", which creates the Angular app and has a couple convenient functions in it.
This is the Angular service that will make calls to get info from the Umbraco API controller we built, via AJAX.
Lastly, we have the AngularJS controller. This will bind the RSS feed we're getting from the service we built to the HTML view. It has a function that's going to call your RSS feed every 30 seconds. Obviously change that to whatever makes sense.
It's a lot of code to do what you want, but I'm pretty positive this could manage what you're looking to accomplish. Let me know if it helps, or if you have any questions!
-Kyle
@Kyle - Whoa! Killer post - Nice work man! H5YR! :)
@Steve - Just wanted to chime in on angularjs resources - I liked the codeschool tutorial as well (which is free btw) http://campus.codeschool.com/courses/shaping-up-with-angular-js/intro - It was an eye opener for me.
EDIT: Can't wait untill the new forum is being released so we can hopefully put an end to code messing up the layouts of the posts and topics...:'(
/Jan
@Jan - Thanks! And hey, great link. I'm going to keep that on hand for people in the future. Looks like it has some practical examples, which are always the best.
is working on a reply...