Skip to content

Hello World Example 1

cdmdotnet edited this page Jan 30, 2018 · 28 revisions

Preface

This guide will walk you through:

  • Adding and configuring CQRS.NET to an existing MVC or WebAPI website, utilising a REST-full API and SignalR for Asynchronous event handling.
  • Creating a HelloWorld command and HelloWorld event
  • Creating a HelloWorld command handler that responds to the HelloWorld command and raises the HelloWorld event - very very simple.
  • Using a Single Page Application style view to call the REST API to create the command, respond with an event and send that event to the browser via SignalR (to simulate a separation of the REST request and the CQRS response).

Shortcut

If you'd rather just get the code, jump straight into our code base for a complete working copy... just enter the connection strings that match your local setup.

Pre-setup

1 Add CQRS.NET

  1. Add the Cqrs.Ninject.WebApi nuget package
  2. Prior to version 2.2, add a new class named EventToHubProxy, with the following content:

// C#

namespace HelloWorld.Code
{
	using cdmdotnet.Logging;
	using Cqrs.Authentication;
	using Cqrs.WebApi.SignalR.Hubs;

	public class EventToHubProxy
		: Cqrs.WebApi.Events.Handlers.EventToHubProxy<string>
	{
		public EventToHubProxy(ILogger logger, INotificationHub notificationHub, IAuthenticationTokenHelper<string> authenticationTokenHelper)
			: base(logger, notificationHub, authenticationTokenHelper)
		{
		}
	}
}
  1. In global.asax.cs, replace the inherited class of HttpApplication with CqrsHttpApplicationWithSignalR <string> (This was CqrsHttpApplication<string, EventToHubProxy> prior to version 2.2) and set the following namespace:

3.1

  • using Cqrs.WebApi;

3.2 Prior to version 2.2, also add:

  • using Cqrs.Ninject.Configuration;
  • using HelloWorld.Code;
  1. Remove the existing Application_Start method and add the follow to Global.asax.cs.

4.1 Prior to version 2.2:

// C#

protected override void ConfigureDefaultDependencyResolver()
{
	DependencyResolver = NinjectDependencyResolver.Current;
}

protected override void ConfigureMvc()
{
	AreaRegistration.RegisterAllAreas();
	GlobalConfiguration.Configure(WebApiConfig.Register);
	FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
	RouteConfig.RegisterRoutes(RouteTable.Routes);
	BundleConfig.RegisterBundles(BundleTable.Bundles);
}

4.2 Version 2.2 or newer:

// C#

protected override void ConfigureMvcOrWebApi()
{
	AreaRegistration.RegisterAllAreas();
	GlobalConfiguration.Configure(WebApiConfig.Register);
	FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
	RouteConfig.RegisterRoutes(RouteTable.Routes);
	BundleConfig.RegisterBundles(BundleTable.Bundles);
}

protected override void RegisterSignalR(Cqrs.Configuration.BusRegistrar registrar)
{
	// Not yet needed.
}

protected override void StartBuses()
{
	// Not yet needed.
}
  1. Remove the now auto added file \App_Start\NinjectWebCommon.cs

  2. Remove the following line from \App_Start\WebApiConfig.cs:

config.MapHttpAttributeRoutes();

  1. Prior to version 2.2, add the following app setting, version 2.2 or newer, update the existing app set to:

<add key="Cqrs.WebApi.AuthenticationTokenType" value="string" />

  1. Add the following to Views/Shared/_Layout.cshtml, right before the </head> tag:

<script src="@Url.Content("~/Client/")" type="text/javascript"></script>

  1. In Views/Shared/_Layout.cshtml move the JQuery bundle from the bottom of the file to one line after the title.
  2. Compile and run the site

2 Add SignalR and the automated CQRS Java-Script client

  1. Add the following to Views/Shared/_Layout.cshtml, before the script from step 1.8.

<!-- HTML -->

@Scripts.Render("~/bundles/SignalR")
<script src="@Url.Content("~/signalr/hubs")" type="text/javascript"></script>
  1. Add the Microsoft.AspNet.SignalR.JS version 2.2.2 nuget package.

  2. Add the following to the RegisterBundles method in \App_Start\BundleConfig.cs

bundles.Add(new ScriptBundle("~/bundles/SignalR").Include("~/Scripts/jquery.signalR-2.2.2.js"));

  1. Add the following to the end of the Startup method in \Startup.cs

new Cqrs.WebApi.SignalR.Hubs.SignalRStartUp().Configuration(app);

  1. Check Views/Shared/_Layout.cshtml and make sure the following is not near the end of the file.

@Scripts.Render("~/bundles/jquery")

This should be just after the title.

  1. Compile and run the site. Open the developer tools for your browser and in the console entering the following command should return an object.

window.api

3 Configure SignalR with CQRS.NET

  1. Add js.cookie.js to your Scripts folder from GitHub
  2. Add the following to the RegisterBundles method in \App_Start\BundleConfig.cs

bundles.Add(new ScriptBundle("~/bundles/Cookies").Include("~/Scripts/js.cookie.js"));

  1. Add the following to Views/Shared/_Layout.cshtml, after the script from step 2.1.

@Scripts.Render("~/bundles/Cookies")

  1. Add the following namespaces to Global.asax.cs
  • using System;
  • using System.Web;
  • using Cqrs.Authentication;
  1. Add the following to Global.asax.cs

5.1 Prior to version 2.2:

// C#

	protected override void Application_BeginRequest(object sender, EventArgs e)
	{
		base.Application_BeginRequest(sender, e);
		HttpCookie authCookie = Request.Cookies[".AspNet.ApplicationCookie"];
		if (authCookie != null)
		{
			// Copy encrypted auth token to X-Token for SignalR
			Response.SetCookie(new HttpCookie("X-Token", authCookie.Value));
			// Pass the auth token to the helper to allow automated authentication handling
			DependencyResolver.Resolve<IAuthenticationTokenHelper<string>>().SetAuthenticationToken(authCookie.Value);
		}
	}

5.2 Version 2.2 or newer:

// C#

	protected override void Application_BeginRequest(object sender, EventArgs e)
	{
		base.Application_BeginRequest(sender, e);
		HttpCookie authCookie = Request.Cookies[".AspNet.ApplicationCookie"];
		if (authCookie != null)
		{
			// Copy encrypted auth token to X-Token for SignalR
			Response.SetCookie(new HttpCookie("X-Token", authCookie.Value));
			// Pass the auth token to the helper to allow automated authentication handling
			Cqrs.Configuration.DependencyResolver.Current.Resolve<IAuthenticationTokenHelper<string>>().SetAuthenticationToken(authCookie.Value);
		}
	}
  1. Add the following to Views/Shared/_Layout.cshtml, right before </head> tag.

<!-- HTML -->

<script type="text/javascript">
	var cqrsNotificationHub;
	$(function () {
		// Declare a proxy to reference the hub.
		cqrsNotificationHub = $.connection.notificationHub;

		// Create a function that the hub can call to notify you when it is setup.
		cqrsNotificationHub.client.registered = function () {
			console.info("Now registered to receive notifications.");
		};

		// Create a function that the hub can call to broadcast messages.
		cqrsNotificationHub.client.notifyEvent = function (event) {
			console.info(event);
		};

		$.connection.hub.qs = { "X-Token": Cookies.get("X-Token") };
		$.connection.logging = false;

		// Start the connection.
		$.connection.hub.start({ withCredentials: false }).done(function () {
		});
	});
</script>
  1. Compile and run the site. Open the developer tools for your browser and check the console. You should see a message Now registered to receive notifications..

4 Configure CQRS.NET, Add a sample controller and View

Logging, CQRS.NET networking (the command bus and event bus)

  1. Add the following to the top of the web.config file

<!-- XML -->

<configSections>
	<section name="LoggerSettings" type="cdmdotnet.Logging.Configuration.LoggerSettingsConfigurationSection, cdmdotnet.Logging" />
</configSections>
<LoggerSettings EnableInfo="false" EnableDebug="false" EnableProgress="false" EnableWarning="true" EnableError="true" EnableFatalError="true" EnableSensitive="false" EnableThreadedLogging="true" ModuleName="MyCompany" Instance="MyApplication" EnvironmentInstance="Server1" Environment="Production" EnableThreadedLoggingOutput="false" UsePerformanceCounters="false" UseApplicationInsightTelemetryHelper="false" SqlDatabaseLogsConnectionStringName="Logging" SqlDatabaseTableName="Logs" />
  1. Add the follow package:
  • Ninject.MVC3 version 3.2.1.0

2.1. Prior to version 2.2, also add the follow packages:

  • Cqrs.Ninject.InProcess.CommandBus
  • Cqrs.Ninject.InProcess.EventBus
  1. Remove the auto added file \App_Start\NinjectWebCommon.cs if it still exists or has been re-added.
  2. Add a new class to the App_Start folder as follows:

// C#

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(HelloWorld.HelloWorldConfiguration), "ConfigureNinject", Order = 40)]
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(HelloWorld.HelloWorldConfiguration), "ConfigureMvc", Order = 60)]

namespace HelloWorld
{
	using System.Web.Mvc;
	using Cqrs.Ninject.Configuration;

	public static class HelloWorldConfiguration
	{
		public static void ConfigureNinject()
		{
			NinjectDependencyResolver.ModulesToLoad.Add(new InProcessCommandBusModule<string>());
			NinjectDependencyResolver.ModulesToLoad.Add(new InProcessEventBusModule<string>());
		}

		public static void ConfigureMvc()
		{
			// Tell ASP.NET MVC 3 to use our Ninject DI Container 
			DependencyResolver.SetResolver(new Ninject.Web.Mvc.NinjectDependencyResolver(((NinjectDependencyResolver)NinjectDependencyResolver.Current).Kernel));
		}
	}
}

4.2. Prior to version 2.2, add the following using namespaces as well:

  • using Cqrs.Ninject.InProcess.CommandBus.Configuration;
  • using Cqrs.Ninject.InProcess.EventBus.Configuration;

4.1. Version 2.2 or newer, replace the RegisterSignalR and StartBuses over-ride methods from Global.asax.cs added in step 1.4.2 with.

// C#

	protected override BusRegistrar RegisterCommandAndEventHandlers()
	{
		HandlerTypes = new[] { typeof(Code.Commands.SayHelloCommand) };
		return base.RegisterCommandAndEventHandlers();
	}

Sample MVC controller, Index HTTP-GET MVC controller method, Index Razor view

  1. Add an Empty MVC 5 Controller named Sample as follows:

// C#

[Authorize]
public class SampleController : Controller
{
	[HttpGet]
	public ActionResult Index()
	{
		return View();
	}
}
  1. Add an Index razor view for the new Sample MVC Controller as follows:

<!-- HTML -->

@{
	ViewBag.Title = "Sample Command and Event via SignalR";
}

<div class="jumbotron">
	<h1>Say Hello</h1>
	<p class="lead">Click the button below to say hello.</p>
	<p id="SayHelloWorld"><a href="javascript: window.api.SampleApi.SayHelloWorld().done(function (data){ console.log(data); });" class="btn btn-primary btn-lg">Say Hello &raquo;</a></p>
</div>

<script>
	$(function () {
		$("#SayHelloWorld").hide();

		// Create a function that the hub can call to notify you when it is setup.
		window.cqrsNotificationHub.client.registered = function () {
			console.info("Now registered to receive notifications.");
			$("#SayHelloWorld").show();
		};
	});
</script>
  1. Replace the navigation menu code in \Views\Shared\_Layout.cshtml as follows

<!-- HTML -->

<ul class="nav navbar-nav">
	<li>@Html.ActionLink("Home", "Index", "Home")</li>
	<!-- This is the new link to the same page -->
	<li>@Html.ActionLink("Sample", "Index", "Sample")</li>
	<li>@Html.ActionLink("About", "About", "Home")</li>
	<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
  1. Compile and run the site. Click on the new Sample link in the menu (you may have to log in). The Say Hello button will appear once SignalR is ready and registered. If you receive a HTTP Error 401.0 - Unauthorized You do not have permission to view this directory or page. error you'll need to note that you are using a version of ASP.NET MVC that lacks authentication, this tutorial is not a guide to using Microsoft ASP.NET MVC.

5 Add Commands, Events and Handlers

Sample WebAPI controller, SayHello HTTP-GET WebAPI controller method, a command, an event a command handler and an event handler.

  1. Create a new command as follows:

// C#

namespace HelloWorld.Code.Commands
{
	using System;
	using System.Collections.Generic;
	using System.Runtime.Serialization;
	using Cqrs.Commands;

	[Serializable]
	[DataContract]
	public class SayHelloCommand : ICommand<string>
	{
		#region Implementation of IMessage

		[DataMember]
		public Guid CorrelationId { get; set; }

		/// <summary>
		/// The originating framework this message was sent from.
		/// </summary>
		[DataMember]
		public string OriginatingFramework { get; set; }

		/// <summary>
		/// The frameworks this <see cref="T:Cqrs.Messages.IMessage"/> has been delivered to/sent via already.
		/// </summary>
		[DataMember]
		public IEnumerable<string> Frameworks { get; set; }

		#endregion

		#region Implementation of IMessageWithAuthenticationToken<string>

		[DataMember]
		public string AuthenticationToken { get; set; }

		#endregion

		#region Implementation of ICommand<string>

		[DataMember]
		public Guid Id { get; set; }

		[DataMember]
		public int ExpectedVersion { get; set; }

		#endregion
	}
}
  1. Create a new event as follows:

NOTE 1 This event has the NotifyCallerEvent attribute. This means the event will be sent to the browser via SignalR

NOTE 2 If you change the namespace of this class, you will need to update the string in step 5.7.

// C#

namespace HelloWorld.Code.Events
{
	using System;
	using System.Collections.Generic;
	using System.Runtime.Serialization;
	using Cqrs.Events;

	[NotifyCallerEvent]
	[Serializable]
	[DataContract]
	public class HelloSaidEvent : IEvent<string>
	{
		#region Implementation of IMessage

		[DataMember]
		public Guid CorrelationId { get; set; }

		/// <summary>
		/// The originating framework this message was sent from.
		/// </summary>
		[DataMember]
		public string OriginatingFramework { get; set; }

		/// <summary>
		/// The frameworks this <see cref="T:Cqrs.Messages.IMessage"/> has been delivered to/sent via already.
		/// </summary>
		[DataMember]
		public IEnumerable<string> Frameworks { get; set; }

		#endregion

		#region Implementation of IMessageWithAuthenticationToken<string>

		[DataMember]
		public string AuthenticationToken { get; set; }

		#endregion

		#region Implementation of IEvent<string>

		[DataMember]
		public Guid Id { get; set; }

		[DataMember]
		public int Version { get; set; }

		[DataMember]
		public DateTimeOffset TimeStamp { get; set; }

		#endregion
	}
}
  1. Add an Empty Web API 2 Controller named SampleApi as follows:

// C#

namespace HelloWorld.Controllers
{
	using System.Web.Http;
	using Cqrs.Commands;
	using Cqrs.Ninject.Configuration;
	using Code.Commands;

	[RoutePrefix("Sample")]
	public class SampleApiController : ApiController
	{
		public SampleApiController(ICommandPublisher<string> commandPublisher)
		{
			CommandPublisher = commandPublisher;
		}

		public SampleApiController()
			: this(NinjectDependencyResolver.Current.Resolve<ICommandPublisher<string>>())
		{
		}

		private ICommandPublisher<string> CommandPublisher { get; set; }

		[Route("SayHelloWorld")]
		[HttpPost]
		public IHttpActionResult SayHelloWorld()
		{
			CommandPublisher.Publish(new SayHelloCommand());
			return Ok();
		}
	}
}
  1. Add a command handler as follows:

// C#

namespace HelloWorld.Code.Commands.Handlers
{
	using Cqrs.Commands;
	using Cqrs.Events;
	using Events;

	public class SayHelloCommandHandler : ICommandHandler<string, SayHelloCommand>
	{
		public SayHelloCommandHandler(IEventPublisher<string> eventPublisher)
		{
			EventPublisher = eventPublisher;
		}

		protected IEventPublisher<string> EventPublisher { get; private set; }

		#region Implementation of IMessageHandler<in SayHelloCommand>

		public void Handle(SayHelloCommand message)
		{
			EventPublisher.Publish(new HelloSaidEvent());
		}

		#endregion
	}
}
  1. Prior to version 2.2, add an event handler by modifying the EventToHubProxy class created in step 1.2:

// C#

namespace HelloWorld.Code
{
	using cdmdotnet.Logging;
	using Cqrs.Authentication;
	using Cqrs.Events;
	using Cqrs.WebApi.SignalR.Hubs;
	using Events;

	public class EventToHubProxy
		: Cqrs.WebApi.Events.Handlers.EventToHubProxy<string>
		, IEventHandler<string, HelloSaidEvent>
	{
		public EventToHubProxy(ILogger logger, INotificationHub notificationHub, IAuthenticationTokenHelper<string> authenticationTokenHelper)
			: base(logger, notificationHub, authenticationTokenHelper)
		{
		}

		#region Implementation of IMessageHandler<in HelloSaidEvent>

		public virtual void Handle(HelloSaidEvent message)
		{
			HandleGenericEvent(message);
		}

		#endregion
	}
}
  1. Modify the Index razor view create in step 4.6 by adding the following as the last element inside the DIV tag:

<!-- HTML -->

<div id="HelloWorldSaid">
	<p style="color: crimson">Congratulations. You've just written and executed your first CQRS command and event!!!<p>
	<p>You've learnt:</p>
	<ul>
		<li>How to configure CQRS.NET.</li>
		<li>How to create a WebAPI controller to create commands and respond to browser initiated events like clicking a button.</li>
		<li>How to configure and communicate with your WebAPI controllers by using the generated Java-script client <pre>window.api[ControllerName][ControllerMethod]</pre></li>
		<li>How to create a command.</li>
		<li>How to create an event.</li>
		<li>How to create a command handler that responds to a command and raises an event.</li>
		<li>How to decorate an event so that it is sent back to the user via SignalR.</li>
		<li>How to respond to events sent via SignalR like displaying a congratulations message.</li>
	</ul>
</div>
  1. Modify the Index razor view create in step 4.6 by replacing the original script block at the end of the file with the following:

<!-- HTML -->

<script>
	$(function () {
		$("#SayHelloWorld").hide();

		// Create a function that the hub can call to notify you when it is setup.
		window.cqrsNotificationHub.client.registered = function () {
			console.info("Now registered to receive notifications.");
			$("#SayHelloWorld").show();
		};

		$("#HelloWorldSaid").hide();

		// Create a function that the hub can call to broadcast messages.
		window.cqrsNotificationHub.client.notifyEvent = function (event) {
			console.info(event);
			switch (event.Type) {
				case "HelloWorld.Code.Events.HelloSaidEvent":
					$("#HelloWorldSaid").show();
					break;
			}
		};
	});
</script>
  1. Compile and run the site. Click on the Sample link in the menu. Click on the Say Hello button. You will be met with a Congratulations message.
Clone this wiki locally