-
Notifications
You must be signed in to change notification settings - Fork 79
Hello World Example 1
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 andHelloWorld
event - Creating a
HelloWorld
command handler that responds to theHelloWorld
command and raises theHelloWorld
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).
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.
- Create a new ASP.NET Web Application (make sure .net framework 4.5 is selected.) as per https://docs.microsoft.com/en-us/aspnet/mvc/overview/security/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on and make sure WebAPI AND MVC are selected
- In our tests we enabled Google External Authentication.
- We've had feedback that updating the nuget packages as part of the steps of creating a new Microsoft ASP.NET application brings in TypeScript, which stops compiling for a lot of people, we currently advise you don't update nuget packages as part of trying to follow the above Microsoft Tutorial.
- Ensure TypeScript tools are installed for Visual Studio
- If you update the nuget packages you may experience the following issue with Microsoft ASP.NET - https://stackoverflow.com/questions/43516779/typescript-on-vs13-stopped-working-after-install-vs15
- Add the
Cqrs.Ninject.WebApi
nuget package - 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)
{
}
}
}
- In
global.asax.cs
, replace the inherited class ofHttpApplication
withCqrsHttpApplicationWithSignalR <string>
(This wasCqrsHttpApplication<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;
- Remove the existing
Application_Start
method and add the follow toGlobal.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.
}
-
Remove the now auto added file
\App_Start\NinjectWebCommon.cs
-
Remove the following line from
\App_Start\WebApiConfig.cs
:
config.MapHttpAttributeRoutes();
- 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" />
- Add the following to
Views/Shared/_Layout.cshtml
, right before the</head>
tag:
<script src="@Url.Content("~/Client/")" type="text/javascript"></script>
- In
Views/Shared/_Layout.cshtml
move the JQuery bundle from the bottom of the file to one line after the title. - Compile and run the site
- 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>
-
Add the
Microsoft.AspNet.SignalR.JS version 2.2.2
nuget package. -
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"));
- Add the following to the end of the
Startup
method in\Startup.cs
new Cqrs.WebApi.SignalR.Hubs.SignalRStartUp().Configuration(app);
- 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.
- 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
- Add
js.cookie.js
to your Scripts folder from GitHub - Add the following to the
RegisterBundles
method in\App_Start\BundleConfig.cs
bundles.Add(new ScriptBundle("~/bundles/Cookies").Include("~/Scripts/js.cookie.js"));
- Add the following to
Views/Shared/_Layout.cshtml
, after the script from step 2.1.
@Scripts.Render("~/bundles/Cookies")
- Add the following namespaces to
Global.asax.cs
using System;
using System.Web;
using Cqrs.Authentication;
- 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);
}
}
- 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>
- 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.
.
- 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" />
- 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
- Remove the auto added file
\App_Start\NinjectWebCommon.cs
if it still exists or has been re-added. - 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();
}
- Add an Empty MVC 5 Controller named
Sample
as follows:
// C#
[Authorize]
public class SampleController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}
}
- 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 »</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>
- 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>
- Compile and run the site. Click on the new
Sample
link in the menu (you may have to log in). TheSay Hello
button will appear once SignalR is ready and registered. If you receive aHTTP 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.
Sample WebAPI controller, SayHello HTTP-GET WebAPI controller method, a command, an event a command handler and an event handler.
- 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
}
}
- 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
}
}
- 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();
}
}
}
- 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
}
}
- 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
}
}
- Modify the
Index
razor view create in step 4.6 by adding the following as the last element inside theDIV
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>
- 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>
- Compile and run the site. Click on the
Sample
link in the menu. Click on theSay Hello
button. You will be met with aCongratulations
message.