As Nathan Cooper notes, SignalR is the best way of achieving this. As I've literally just built what you've described, I'll give you a detailed rundown of what you need to do..
Create a new ASP.NET MVC project, and install ASP.NET SignalR using NuGet as well as Tweetinvi
Right click on the App_Start folder and add a new OWIN Startup class (this should be listed in the contextual menu if you have installed SignalR using NuGet).
Your OWIN startup class should look like this:
[assembly: OwinStartup(typeof(TwitterClient.Web.App_Start.Startup))]
namespace TwitterClient.Web.App_Start
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
}
Add a new Folder to the project called "Hubs" and add a new SignalR Hub class (this should also be available as a template in the new file dialog in Visual Studio).
Create a new class called "TwitterConnection" wherever you feel like in the project. In the constructor for this class, do all of the stuff you did in your console application to connect to the Twitter API with Tweetinvi. Usually when you broadcast data from server to client in SignalR, you do it from within the Hub class, but you can obtain a reference to the SignalR hub outside of the hub class itself by using GlobalHost.ConnectionManager.GetHubContext<HUBNAME>();
where HUBNAME is the name of your hub. So your TwitterConnection
class should look something like this:
public class TwitterConnection {
private string _consumerKey = ConfigurationManager.AppSettings.Get("consumerKey");
private string _consumerSecret = ConfigurationManager.AppSettings.Get("consumerSecret");
private string _accessKey = ConfigurationManager.AppSettings.Get("accessToken");
private string _accessToken = ConfigurationManager.AppSettings.Get("accessTokenSecret");
private IHubContext _context = GlobalHost.ConnectionManager.GetHubContext<TwitterHub>();
public TwitterConnection()
{
// Access the filtered stream
var filteredStream = Stream.CreateFilteredStream();
filteredStream.MatchingTweetReceived += (sender, args) =>
{
_context.Clients.All.broadcast(args.Tweet.Text);
};
filteredStream.StartStreamMatchingAllConditions();
}
}
In terms of the server-side stuff, you need to find a way of ensuring that there is only one instance of a stream open to Twitter at any one time. My quick and dirty way of doing this was to use Task.Factory.StartNew
to create a new Task to manage to stream in the Global.asax
file:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// Start a new instance of the TwitterConnection class
Task.Factory.StartNew(() => new TwitterConnection());
}
Finally, you need to hook up the client-side element of SignalR. In your MVC Layout view (i.e. Views/Shared/_Layout.cshtml
), add in a reference at the bottom of the HTML markup to the SignalR JavaScript library, the generated Hub proxy and to your external JavaScript file where your boilerplate SignalR JavaScript will go:
<!--Reference the SignalR library. -->
<script src="../../Scripts/jquery.signalR-2.2.0.min.js"></script>
<!--Reference the autogenerated SignalR hub script. -->
<script src="signalr/hubs"></script>
<script src="../../Scripts/application.js"></script>
Finally, your boilerplate code in application.js
(or whatever you want to call it), will look something like this:
// document ready shorthand
$(function () {
// obtain reference to the hub proxy and hub itself
var theHub = $.connection.twitterHub;
// this is the function that the server will call to broadcast new tweets
theHub.client.broadcast = function (tweet) {
var item = '<li>' + tweet.text + '</li>';
$('ul.tweets').prepend(item);
};
// this is a function that indicates that connection to the hub has been successful
$.connection.hub.start().done(function () {
console.log("connected");
});
});
Your Index.cshtml
file will simply have an empty <ul>
in it, where new tweets will be prepended to as and when they are received:
@{
ViewBag.Title = "Home Page";
}
<div class="row">
<div class="col-md-12">
<ul class="tweets"></ul>
</div>
</div>