├── Pics └── 2.png ├── .nuget ├── NuGet.exe ├── NuGet.Config └── NuGet.targets ├── CQRSDemo.Web ├── Views │ ├── _ViewStart.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ └── _Layout.cshtml │ ├── Home │ │ ├── Index.cshtml │ │ ├── Add.cshtml │ │ └── Edit.cshtml │ └── Web.config ├── favicon.ico ├── Images │ ├── accent.png │ ├── bullet.png │ ├── heroAccent.png │ ├── orderedList0.png │ ├── orderedList1.png │ ├── orderedList2.png │ ├── orderedList3.png │ ├── orderedList4.png │ ├── orderedList5.png │ ├── orderedList6.png │ ├── orderedList7.png │ ├── orderedList8.png │ └── orderedList9.png ├── Global.asax ├── Scripts │ ├── _references.js │ ├── jquery.unobtrusive-ajax.min.js │ ├── jquery.validate.unobtrusive.min.js │ ├── jquery.unobtrusive-ajax.js │ └── jquery.validate.unobtrusive.js ├── Content │ ├── themes │ │ └── base │ │ │ ├── images │ │ │ ├── ui-icons_222222_256x240.png │ │ │ ├── ui-icons_2e83ff_256x240.png │ │ │ ├── ui-icons_454545_256x240.png │ │ │ ├── ui-icons_888888_256x240.png │ │ │ ├── ui-icons_cd0a0a_256x240.png │ │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ │ └── ui-bg_highlight-soft_75_cccccc_1x100.png │ │ │ ├── minified │ │ │ ├── images │ │ │ │ ├── ui-icons_222222_256x240.png │ │ │ │ ├── ui-icons_2e83ff_256x240.png │ │ │ │ ├── ui-icons_454545_256x240.png │ │ │ │ ├── ui-icons_888888_256x240.png │ │ │ │ ├── ui-icons_cd0a0a_256x240.png │ │ │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ │ │ └── ui-bg_highlight-soft_75_cccccc_1x100.png │ │ │ ├── jquery.ui.selectable.min.css │ │ │ ├── jquery.ui.progressbar.min.css │ │ │ ├── jquery.ui.autocomplete.min.css │ │ │ ├── jquery.ui.core.min.css │ │ │ ├── jquery.ui.accordion.min.css │ │ │ ├── jquery.ui.tabs.min.css │ │ │ ├── jquery.ui.resizable.min.css │ │ │ ├── jquery.ui.slider.min.css │ │ │ ├── jquery.ui.dialog.min.css │ │ │ ├── jquery.ui.button.min.css │ │ │ ├── jquery.ui.datepicker.min.css │ │ │ └── jquery.ui.theme.min.css │ │ │ ├── jquery.ui.all.css │ │ │ ├── jquery.ui.selectable.css │ │ │ ├── jquery.ui.progressbar.css │ │ │ ├── jquery.ui.base.css │ │ │ ├── jquery.ui.accordion.css │ │ │ ├── jquery.ui.resizable.css │ │ │ ├── jquery.ui.slider.css │ │ │ ├── jquery.ui.autocomplete.css │ │ │ ├── jquery.ui.dialog.css │ │ │ ├── jquery.ui.tabs.css │ │ │ ├── jquery.ui.core.css │ │ │ ├── jquery.ui.button.css │ │ │ └── jquery.ui.datepicker.css │ └── Site.css ├── App_Start │ ├── FilterConfig.cs │ ├── WebApiConfig.cs │ ├── RouteConfig.cs │ ├── AuthConfig.cs │ └── BundleConfig.cs ├── Global.asax.cs ├── Web.Debug.config ├── Web.Release.config ├── Properties │ └── AssemblyInfo.cs ├── Filters │ └── InitializeSimpleMembershipAttribute.cs ├── Controllers │ └── HomeController.cs ├── packages.config ├── Models │ └── AccountModels.cs └── Web.config ├── CQRSFramework ├── Events │ ├── IEvent.cs │ ├── IHandle.cs │ ├── IDomainEvent.cs │ ├── DiaryItemDeletedEvent.cs │ ├── DiaryItemRenamedEvent.cs │ ├── DiaryItemToChangedEvent.cs │ ├── DiaryItemFromChangedEvent.cs │ ├── DiaryItemDescriptionChangedEvent.cs │ ├── DomainEvent.cs │ ├── Storage │ │ ├── IEventStorage.cs │ │ └── InMemoryEventStorage.cs │ └── DiaryItemCreatedEvent.cs ├── packages.config ├── Bus │ ├── ICommandBus.cs │ ├── IEventBus.cs │ ├── IEventHandlerFactory.cs │ ├── ICommandHandlerFactory.cs │ ├── EventBus.cs │ ├── CommandBus.cs │ ├── StructureMapEventHandlerFactory.cs │ └── StructureMapCommandHandlerFactory.cs ├── Snapshots │ ├── ISnapshot.cs │ ├── ISnapshotOrignator.cs │ ├── BaseSnapshot.cs │ └── DiaryItemSnapshot.cs ├── EventHandlers │ ├── IEventHandler.cs │ ├── DiaryItemDeletedEventHandler.cs │ ├── DiaryItemRenamedEventHandler.cs │ ├── DiaryItemFromChangedEventHandler.cs │ ├── DiaryItemToChangedEventHandler.cs │ ├── DiaryItemDescriptionChangedEventHandler.cs │ └── DiaryIteamCreatedEventHandler.cs ├── Exceptions │ ├── ConcurrencyException.cs │ ├── AggregateNotFoundException.cs │ ├── UnregisteredDomainEventException.cs │ └── UnregisteredDomainCommandException.cs ├── CommandHandlers │ ├── ICommandHandler.cs │ ├── DeleteItemCommandHandler.cs │ ├── CreateItemCommandHandler.cs │ └── ChangeItemCommandHandler.cs ├── Domain │ ├── IAggregateRoot .cs │ ├── IEntity.cs │ ├── IEventProvider.cs │ ├── AggregateRoot.cs │ └── DiaryItem.cs ├── Commands │ ├── DeleteItemCommand.cs │ ├── ICommand.cs │ ├── Command.cs │ ├── CreateItemCommand.cs │ └── ChangeItemCommand.cs ├── Repositories │ ├── IDomainRepository.cs │ └── DomainRepository.cs ├── Storage │ ├── IStorage.cs │ └── InMemoryStorage.cs ├── Utils │ └── TypeConverter.cs ├── Services │ └── ApplicationServices │ │ └── DiaryItemDto.cs ├── Properties │ └── AssemblyInfo.cs ├── ServiceLocator.cs └── CQRSFramework.csproj ├── packages └── repositories.config ├── .gitattributes ├── CQRSDemo.sln └── .gitignore /Pics/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/Pics/2.png -------------------------------------------------------------------------------- /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/.nuget/NuGet.exe -------------------------------------------------------------------------------- /CQRSDemo.Web/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Views/Shared/_Layout.cshtml"; 3 | } -------------------------------------------------------------------------------- /CQRSDemo.Web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/favicon.ico -------------------------------------------------------------------------------- /CQRSDemo.Web/Images/accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Images/accent.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Images/bullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Images/bullet.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Codebehind="Global.asax.cs" Inherits="CQRSDemo.Web.MvcApplication" Language="C#" %> 2 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Images/heroAccent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Images/heroAccent.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Scripts/_references.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Scripts/_references.js -------------------------------------------------------------------------------- /CQRSDemo.Web/Images/orderedList0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Images/orderedList0.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Images/orderedList1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Images/orderedList1.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Images/orderedList2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Images/orderedList2.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Images/orderedList3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Images/orderedList3.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Images/orderedList4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Images/orderedList4.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Images/orderedList5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Images/orderedList5.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Images/orderedList6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Images/orderedList6.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Images/orderedList7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Images/orderedList7.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Images/orderedList8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Images/orderedList8.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Images/orderedList9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Images/orderedList9.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /CQRSFramework/Events/IEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Events 4 | { 5 | public interface IEvent 6 | { 7 | Guid Id { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CQRSFramework/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /CQRSFramework/Events/IHandle.cs: -------------------------------------------------------------------------------- 1 | namespace CQRSFramework.Events 2 | { 3 | public interface IHandle where TEvent : IEvent 4 | { 5 | void Handle(TEvent e); 6 | } 7 | } -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/minified/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/minified/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/minified/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/minified/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/minified/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/minified/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/minified/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/minified/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/minified/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/minified/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/minified/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/minified/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /CQRSFramework/Events/IDomainEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Events 4 | { 5 | public interface IDomainEvent : IEvent 6 | { 7 | Guid SourceId { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/repositories.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/HEAD/CQRSDemo.Web/Content/themes/base/minified/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /CQRSFramework/Bus/ICommandBus.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Commands; 2 | 3 | namespace CQRSFramework.Bus 4 | { 5 | public interface ICommandBus 6 | { 7 | void Send(T command) where T : Command; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CQRSFramework/Bus/IEventBus.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Events; 2 | 3 | namespace CQRSFramework.Bus 4 | { 5 | public interface IEventBus 6 | { 7 | void Publish(T @event) where T : DomainEvent; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CQRSFramework/Snapshots/ISnapshot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Snapshots 4 | { 5 | public interface ISnapshot 6 | { 7 | Guid AggregateRootId { get; } 8 | long Version { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /CQRSFramework/Snapshots/ISnapshotOrignator.cs: -------------------------------------------------------------------------------- 1 | namespace CQRSFramework.Snapshots 2 | { 3 | public interface ISnapshotOrignator 4 | { 5 | ISnapshot CreateSnapshot(); 6 | void BuildFromSnapshot(ISnapshot snapshot); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /CQRSFramework/EventHandlers/IEventHandler.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Events; 2 | 3 | namespace CQRSFramework.EventHandlers 4 | { 5 | public interface IEventHandler where TEvent : IEvent 6 | { 7 | void Handle(TEvent @event); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CQRSFramework/Exceptions/ConcurrencyException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Exceptions 4 | { 5 | public class ConcurrencyException : Exception 6 | { 7 | public ConcurrencyException(string message) : base(message) { } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CQRSFramework/CommandHandlers/ICommandHandler.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Commands; 2 | 3 | namespace CQRSFramework.CommandHandlers 4 | { 5 | public interface ICommandHandler where TCommand : Command 6 | { 7 | void Execute(TCommand command); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CQRSFramework/Exceptions/AggregateNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Exceptions 4 | { 5 | public class AggregateNotFoundException : Exception 6 | { 7 | public AggregateNotFoundException(string message) : base(message) { } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model System.Web.Mvc.HandleErrorInfo 2 | 3 | @{ 4 | ViewBag.Title = "Error"; 5 | } 6 | 7 | 8 | Error. 9 | An error occurred while processing your request. 10 | 11 | -------------------------------------------------------------------------------- /CQRSFramework/Snapshots/BaseSnapshot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Snapshots 4 | { 5 | public abstract class BaseSnapshot : ISnapshot 6 | { 7 | public Guid AggregateRootId { get; internal set; } 8 | public long Version { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /CQRSFramework/Domain/IAggregateRoot .cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace CQRSFramework.Domain 8 | { 9 | public interface IAggregateRoot : IEntity 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CQRSFramework/Commands/DeleteItemCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Commands 4 | { 5 | public class DeleteItemCommand : Command 6 | { 7 | public DeleteItemCommand(Guid id, int version) 8 | : base(id, version) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /CQRSFramework/Domain/IEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace CQRSFramework.Domain 8 | { 9 | public interface IEntity 10 | { 11 | Guid ID { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CQRSFramework/Exceptions/UnregisteredDomainEventException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Exceptions 4 | { 5 | public class UnregisteredDomainEventException : Exception 6 | { 7 | public UnregisteredDomainEventException(string message) : base(message) { } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CQRSFramework/Commands/ICommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace CQRSFramework.Commands 8 | { 9 | public interface ICommand 10 | { 11 | Guid ID { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CQRSFramework/Events/DiaryItemDeletedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Events 4 | { 5 | public class DiaryItemDeletedEvent : DomainEvent 6 | { 7 | public DiaryItemDeletedEvent(Guid aggregateId) 8 | { 9 | SourceId = aggregateId; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/jquery.ui.selectable.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.8.20 - 2012-04-30 2 | * https://github.com/jquery/jquery-ui 3 | * Includes: jquery.ui.selectable.css 4 | * Copyright (c) 2012 AUTHORS.txt; Licensed MIT */ 5 | .ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black} -------------------------------------------------------------------------------- /CQRSFramework/Exceptions/UnregisteredDomainCommandException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Exceptions 4 | { 5 | public class UnregisteredDomainCommandException : Exception 6 | { 7 | public UnregisteredDomainCommandException(string message) : base(message) { } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CQRSFramework/Bus/IEventHandlerFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CQRSFramework.EventHandlers; 3 | using CQRSFramework.Events; 4 | 5 | namespace CQRSFramework.Bus 6 | { 7 | public interface IEventHandlerFactory 8 | { 9 | IEnumerable> GetHandlers() where T : IEvent; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CQRSDemo.Web/App_Start/FilterConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Mvc; 3 | 4 | namespace CQRSDemo.Web 5 | { 6 | public class FilterConfig 7 | { 8 | public static void RegisterGlobalFilters(GlobalFilterCollection filters) 9 | { 10 | filters.Add(new HandleErrorAttribute()); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /CQRSFramework/Bus/ICommandHandlerFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CQRSFramework.CommandHandlers; 3 | using CQRSFramework.Commands; 4 | 5 | namespace CQRSFramework.Bus 6 | { 7 | public interface ICommandHandlerFactory 8 | { 9 | IEnumerable> GetHandlers() where T : Command; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CQRSFramework/Repositories/IDomainRepository.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Domain; 2 | using System; 3 | 4 | namespace CQRSFramework.Repositories 5 | { 6 | public interface IDomainRepository where T : AggregateRoot, new() 7 | { 8 | void Save(AggregateRoot aggregate, int expectedVersion); 9 | T GetById(Guid id); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/jquery.ui.progressbar.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.8.20 - 2012-04-30 2 | * https://github.com/jquery/jquery-ui 3 | * Includes: jquery.ui.progressbar.css 4 | * Copyright (c) 2012 AUTHORS.txt; Licensed MIT */ 5 | .ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%} -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/jquery.ui.all.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI CSS Framework 1.8.20 3 | * 4 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 5 | * Licensed under the MIT license. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Theming 9 | */ 10 | @import "jquery.ui.base.css"; 11 | @import "jquery.ui.theme.css"; 12 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/jquery.ui.selectable.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Selectable 1.8.20 3 | * 4 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 5 | * Licensed under the MIT license. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Selectable#theming 9 | */ 10 | .ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } 11 | -------------------------------------------------------------------------------- /CQRSFramework/Events/DiaryItemRenamedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Events 4 | { 5 | public class DiaryItemRenamedEvent : DomainEvent 6 | { 7 | public string Title { get; internal set; } 8 | public DiaryItemRenamedEvent(Guid aggregateId, string title) 9 | { 10 | SourceId = aggregateId; 11 | Title = title; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CQRSFramework/Storage/IStorage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using CQRSFramework.Services.ApplicationServices; 4 | 5 | namespace CQRSFramework.Storage 6 | { 7 | public interface IStorage 8 | { 9 | DiaryItemDto GetById(Guid id); 10 | void Add(DiaryItemDto item); 11 | void Delete(Guid id); 12 | List GetItems(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CQRSFramework/Events/DiaryItemToChangedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Events 4 | { 5 | public class DiaryItemToChangedEvent : DomainEvent 6 | { 7 | public DateTime To { get; internal set; } 8 | public DiaryItemToChangedEvent(Guid aggregateId, DateTime to) 9 | { 10 | SourceId = aggregateId; 11 | To = to; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CQRSFramework/Events/DiaryItemFromChangedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Events 4 | { 5 | public class DiaryItemFromChangedEvent : DomainEvent 6 | { 7 | public DateTime From { get; internal set; } 8 | public DiaryItemFromChangedEvent(Guid aggregateId, DateTime from) 9 | { 10 | SourceId = aggregateId; 11 | From = from; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/jquery.ui.progressbar.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Progressbar 1.8.20 3 | * 4 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 5 | * Licensed under the MIT license. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Progressbar#theming 9 | */ 10 | .ui-progressbar { height:2em; text-align: left; overflow: hidden; } 11 | .ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } -------------------------------------------------------------------------------- /CQRSFramework/Commands/Command.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Commands 4 | { 5 | [Serializable] 6 | public abstract class Command :ICommand 7 | { 8 | public Guid ID { get; private set; } 9 | 10 | public int Version { get; private set; } 11 | 12 | protected Command(Guid id, int version) 13 | { 14 | ID = id; 15 | Version = version; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CQRSFramework/Domain/IEventProvider.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Events; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace CQRSFramework.Domain 9 | { 10 | public interface IEventProvider 11 | { 12 | void LoadsFromHistory(IEnumerable history); 13 | IEnumerable GetUncommittedChanges(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CQRSFramework/Events/DiaryItemDescriptionChangedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Events 4 | { 5 | public class DiaryItemDescriptionChangedEvent : DomainEvent 6 | { 7 | public string Description { get; internal set; } 8 | public DiaryItemDescriptionChangedEvent(Guid aggregateId, string description) 9 | { 10 | SourceId = aggregateId; 11 | Description = description; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CQRSFramework/Events/DomainEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Events 4 | { 5 | [Serializable] 6 | public abstract class DomainEvent : IDomainEvent 7 | { 8 | public int Version; 9 | 10 | #region IDomainEvent Member 11 | public Guid SourceId { get; set; } 12 | #endregion 13 | 14 | #region IEvent Members 15 | 16 | public Guid Id { get; set; } 17 | 18 | #endregion 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Index"; 3 | Layout = "~/Views/Shared/_Layout.cshtml"; 4 | } 5 | 6 | Diary Items 7 | 8 | 9 | @foreach (var item in Model) 10 | { 11 | 12 | @item.Title 13 | @item.Version 14 | @Html.ActionLink("Edit","Edit",new {id=item.Id}) 15 | @Html.ActionLink("Delete","Delete",new {id=item.Id}) 16 | 17 | } 18 | 19 | 20 | @Html.ActionLink("Add","Add") -------------------------------------------------------------------------------- /CQRSFramework/Events/Storage/IEventStorage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using CQRSFramework.Domain; 4 | using CQRSFramework.Snapshots; 5 | 6 | namespace CQRSFramework.Events.Storage 7 | { 8 | public interface IEventStorage 9 | { 10 | IEnumerable GetEvents(Guid aggregateId); 11 | void Save(AggregateRoot aggregate); 12 | 13 | T GetSnapshot(Guid aggregateId) where T : BaseSnapshot; 14 | void SaveSnapshot(ISnapshot snapshot); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CQRSFramework/Utils/TypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | 4 | namespace CQRSFramework.Utils 5 | { 6 | public class TypeConverter 7 | { 8 | public static Action Convert(Action myActionT) 9 | { 10 | if (myActionT == null) return null; 11 | return new Action(o => myActionT((T)o)); 12 | } 13 | 14 | public static dynamic ChangeTo(dynamic source, Type dest) 15 | { 16 | return System.Convert.ChangeType(source, dest); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CQRSDemo.Web/App_Start/WebApiConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web.Http; 5 | 6 | namespace CQRSDemo.Web 7 | { 8 | public static class WebApiConfig 9 | { 10 | public static void Register(HttpConfiguration config) 11 | { 12 | config.Routes.MapHttpRoute( 13 | name: "DefaultApi", 14 | routeTemplate: "api/{controller}/{id}", 15 | defaults: new { id = RouteParameter.Optional } 16 | ); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /CQRSFramework/EventHandlers/DiaryItemDeletedEventHandler.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Events; 2 | using CQRSFramework.Storage; 3 | 4 | namespace CQRSFramework.EventHandlers 5 | { 6 | public class DiaryItemDeletedEventHandler : IEventHandler 7 | { 8 | private readonly IStorage _queryStorage; 9 | public DiaryItemDeletedEventHandler(IStorage queryStorage) 10 | { 11 | _queryStorage = queryStorage; 12 | } 13 | 14 | public void Handle(DiaryItemDeletedEvent @event) 15 | { 16 | _queryStorage.Delete(@event.SourceId); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CQRSDemo.Web/App_Start/RouteConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Mvc; 6 | using System.Web.Routing; 7 | 8 | namespace CQRSDemo.Web 9 | { 10 | public class RouteConfig 11 | { 12 | public static void RegisterRoutes(RouteCollection routes) 13 | { 14 | routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 15 | 16 | routes.MapRoute( 17 | name: "Default", 18 | url: "{controller}/{action}/{id}", 19 | defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } 20 | ); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/jquery.ui.autocomplete.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.8.20 - 2012-04-30 2 | * https://github.com/jquery/jquery-ui 3 | * Includes: jquery.ui.autocomplete.css 4 | * Copyright (c) 2012 AUTHORS.txt; Licensed MIT */ 5 | .ui-autocomplete{position:absolute;cursor:default}* html .ui-autocomplete{width:1px}.ui-menu{list-style:none;padding:2px;margin:0;display:block;float:left}.ui-menu .ui-menu{margin-top:-3px}.ui-menu .ui-menu-item{margin:0;padding:0;zoom:1;float:left;clear:left;width:100%}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:.2em .4em;line-height:1.5;zoom:1}.ui-menu .ui-menu-item a.ui-state-hover,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px} -------------------------------------------------------------------------------- /CQRSFramework/EventHandlers/DiaryItemRenamedEventHandler.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Events; 2 | using CQRSFramework.Storage; 3 | 4 | namespace CQRSFramework.EventHandlers 5 | { 6 | public class DiaryItemRenamedEventHandler : IEventHandler 7 | { 8 | private readonly IStorage _queryStorage; 9 | public DiaryItemRenamedEventHandler(IStorage queryStorage) 10 | { 11 | _queryStorage = queryStorage; 12 | } 13 | public void Handle(DiaryItemRenamedEvent @event) 14 | { 15 | var item = _queryStorage.GetById(@event.SourceId); 16 | item.Title = @event.Title; 17 | item.Version = @event.Version; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CQRSFramework/Events/DiaryItemCreatedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Events 4 | { 5 | public class DiaryItemCreatedEvent : DomainEvent 6 | { 7 | public string Title { get; internal set; } 8 | public DateTime From { get; internal set; } 9 | public DateTime To { get; internal set; } 10 | public string Description { get;internal set; } 11 | 12 | public DiaryItemCreatedEvent(Guid aggregateId, string title, 13 | string description, DateTime from, DateTime to) 14 | { 15 | SourceId = aggregateId; 16 | Title = title; 17 | From = from; 18 | To = to; 19 | Description = description; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CQRSFramework/EventHandlers/DiaryItemFromChangedEventHandler.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Events; 2 | using CQRSFramework.Storage; 3 | 4 | namespace CQRSFramework.EventHandlers 5 | { 6 | public class DiaryItemFromChangedEventHandler : IEventHandler 7 | { 8 | private readonly IStorage _queryStorage; 9 | public DiaryItemFromChangedEventHandler(IStorage queryStorage) 10 | { 11 | _queryStorage = queryStorage; 12 | } 13 | 14 | public void Handle(DiaryItemFromChangedEvent @event) 15 | { 16 | var item = _queryStorage.GetById(@event.SourceId); 17 | item.From = @event.From; 18 | item.Version = @event.Version; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CQRSFramework/Commands/CreateItemCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Commands 4 | { 5 | public class CreateItemCommand : Command 6 | { 7 | public string Title { get; internal set; } 8 | public string Description { get; internal set; } 9 | public DateTime From { get; internal set; } 10 | public DateTime To { get; internal set; } 11 | 12 | public CreateItemCommand(Guid aggregateId, string title, 13 | string description, int version, DateTime from, DateTime to) 14 | : base(aggregateId, version) 15 | { 16 | Title = title; 17 | Description = description; 18 | From = from; 19 | To = to; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/jquery.ui.base.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI CSS Framework 1.8.20 3 | * 4 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 5 | * Licensed under the MIT license. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Theming 9 | */ 10 | @import url("jquery.ui.core.css"); 11 | 12 | @import url("jquery.ui.accordion.css"); 13 | @import url("jquery.ui.autocomplete.css"); 14 | @import url("jquery.ui.button.css"); 15 | @import url("jquery.ui.datepicker.css"); 16 | @import url("jquery.ui.dialog.css"); 17 | @import url("jquery.ui.progressbar.css"); 18 | @import url("jquery.ui.resizable.css"); 19 | @import url("jquery.ui.selectable.css"); 20 | @import url("jquery.ui.slider.css"); 21 | @import url("jquery.ui.tabs.css"); 22 | -------------------------------------------------------------------------------- /CQRSFramework/EventHandlers/DiaryItemToChangedEventHandler.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Events; 2 | using CQRSFramework.Services; 3 | using CQRSFramework.Storage; 4 | 5 | namespace CQRSFramework.EventHandlers 6 | { 7 | public class DiaryItemToChangedEventHandler : IEventHandler 8 | { 9 | private readonly IStorage _queryStorage; 10 | 11 | public DiaryItemToChangedEventHandler(IStorage queryStorage) 12 | { 13 | _queryStorage = queryStorage; 14 | } 15 | 16 | public void Handle(DiaryItemToChangedEvent @event) 17 | { 18 | var item = _queryStorage.GetById(@event.SourceId); 19 | item.To = @event.To; 20 | item.Version = @event.Version; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /CQRSFramework/EventHandlers/DiaryItemDescriptionChangedEventHandler.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Events; 2 | using CQRSFramework.Storage; 3 | 4 | namespace CQRSFramework.EventHandlers 5 | { 6 | public class DiaryItemDescriptionChangedEventHandler : IEventHandler 7 | { 8 | private readonly IStorage _queryStorage; 9 | public DiaryItemDescriptionChangedEventHandler(IStorage queryStorage) 10 | { 11 | _queryStorage = queryStorage; 12 | } 13 | 14 | public void Handle(DiaryItemDescriptionChangedEvent @event) 15 | { 16 | var item = _queryStorage.GetById(@event.SourceId); 17 | item.Description = @event.Description; 18 | item.Version = @event.Version; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CQRSFramework/Bus/EventBus.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Events; 2 | 3 | namespace CQRSFramework.Bus 4 | { 5 | // EventBus的实现 6 | public class EventBus : IEventBus 7 | { 8 | private readonly IEventHandlerFactory _eventHandlerFactory; 9 | 10 | public EventBus(IEventHandlerFactory eventHandlerFactory) 11 | { 12 | _eventHandlerFactory = eventHandlerFactory; 13 | } 14 | 15 | public void Publish(T @event) where T : DomainEvent 16 | { 17 | // 获得对应的EventHandle来处理事件 18 | var handlers = _eventHandlerFactory.GetHandlers(); 19 | foreach (var eventHandler in handlers) 20 | { 21 | // 对事件进行处理 22 | eventHandler.Handle(@event); 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CQRSFramework/Bus/CommandBus.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Commands; 2 | 3 | namespace CQRSFramework.Bus 4 | { 5 | // CommandBus 的实现 6 | public class CommandBus : ICommandBus 7 | { 8 | private readonly ICommandHandlerFactory _commandHandlerFactory; 9 | 10 | public CommandBus(ICommandHandlerFactory commandHandlerFactory) 11 | { 12 | _commandHandlerFactory = commandHandlerFactory; 13 | } 14 | 15 | public void Send(T command) where T : Command 16 | { 17 | // 获得对应的CommandHandle来对命令进行处理 18 | var handlers = _commandHandlerFactory.GetHandlers(); 19 | 20 | foreach (var handler in handlers) 21 | { 22 | // 处理命令 23 | handler.Execute(command); 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CQRSFramework/Commands/ChangeItemCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace CQRSFramework.Commands 8 | { 9 | public class ChangeItemCommand : Command 10 | { 11 | public DateTime To { get; internal set; } 12 | public DateTime From { get; internal set; } 13 | public string Title { get; internal set; } 14 | public string Description { get; internal set; } 15 | 16 | public ChangeItemCommand(Guid aggregateId, string title, string description, DateTime from, DateTime to, int version) 17 | : base(aggregateId, version) 18 | { 19 | To = to; 20 | From = from; 21 | Description = description; 22 | Title = title; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CQRSFramework/Storage/InMemoryStorage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CQRSFramework.Services.ApplicationServices; 5 | 6 | namespace CQRSFramework.Storage 7 | { 8 | public class InMemoryStorage : IStorage 9 | { 10 | private static readonly List Items = new List(); 11 | 12 | public DiaryItemDto GetById(Guid id) 13 | { 14 | return Items.FirstOrDefault(a => a.Id == id); 15 | } 16 | 17 | public void Add(DiaryItemDto item) 18 | { 19 | Items.Add(item); 20 | } 21 | 22 | public void Delete(Guid id) 23 | { 24 | Items.RemoveAll(i => i.Id == id); 25 | } 26 | 27 | public List GetItems() 28 | { 29 | return Items; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/jquery.ui.core.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.8.20 - 2012-04-30 2 | * https://github.com/jquery/jquery-ui 3 | * Includes: jquery.ui.core.css 4 | * Copyright (c) 2012 AUTHORS.txt; Licensed MIT */ 5 | .ui-helper-hidden{display:none}.ui-helper-hidden-accessible{position:absolute!important;clip:rect(1px);clip:rect(1px,1px,1px,1px)}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{zoom:1}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:absolute;top:0;left:0;width:100%;height:100%} -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/jquery.ui.accordion.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.8.20 - 2012-04-30 2 | * https://github.com/jquery/jquery-ui 3 | * Includes: jquery.ui.accordion.css 4 | * Copyright (c) 2012 AUTHORS.txt; Licensed MIT */ 5 | .ui-accordion{width:100%}.ui-accordion .ui-accordion-header{cursor:pointer;position:relative;margin-top:1px;zoom:1}.ui-accordion .ui-accordion-li-fix{display:inline}.ui-accordion .ui-accordion-header-active{border-bottom:0!important}.ui-accordion .ui-accordion-header a{display:block;font-size:1em;padding:.5em .5em .5em .7em}.ui-accordion-icons .ui-accordion-header a{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;margin-top:-2px;position:relative;top:1px;margin-bottom:2px;overflow:auto;display:none;zoom:1}.ui-accordion .ui-accordion-content-active{display:block} -------------------------------------------------------------------------------- /CQRSFramework/Snapshots/DiaryItemSnapshot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSFramework.Snapshots 4 | { 5 | public class DiaryItemSnapshot : BaseSnapshot 6 | { 7 | public string Title { get; internal set; } 8 | public string Description { get;internal set; } 9 | public DateTime From { get; internal set; } 10 | public DateTime To { get; internal set;} 11 | 12 | public int EventVersion { get; set; } 13 | 14 | public DiaryItemSnapshot(Guid aggregateRootId, string title,string description, DateTime from, DateTime to,int version) 15 | { 16 | Title = title; 17 | AggregateRootId = aggregateRootId; 18 | Title = title; 19 | From = from; 20 | To = to; 21 | Version = version; 22 | EventVersion = version; 23 | Description = description; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Global.asax.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Http; 6 | using System.Web.Mvc; 7 | using System.Web.Optimization; 8 | using System.Web.Routing; 9 | 10 | namespace CQRSDemo.Web 11 | { 12 | // Note: For instructions on enabling IIS6 or IIS7 classic mode, 13 | // visit http://go.microsoft.com/?LinkId=9394801 14 | 15 | public class MvcApplication : System.Web.HttpApplication 16 | { 17 | protected void Application_Start() 18 | { 19 | AreaRegistration.RegisterAllAreas(); 20 | 21 | WebApiConfig.Register(GlobalConfiguration.Configuration); 22 | FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 23 | RouteConfig.RegisterRoutes(RouteTable.Routes); 24 | BundleConfig.RegisterBundles(BundleTable.Bundles); 25 | AuthConfig.RegisterAuth(); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/jquery.ui.tabs.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.8.20 - 2012-04-30 2 | * https://github.com/jquery/jquery-ui 3 | * Includes: jquery.ui.tabs.css 4 | * Copyright (c) 2012 AUTHORS.txt; Licensed MIT */ 5 | .ui-tabs{position:relative;padding:.2em;zoom:1}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:1px;margin:0 .2em 1px 0;border-bottom:0!important;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav li a{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-selected{margin-bottom:0;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-selected a,.ui-tabs .ui-tabs-nav li.ui-state-disabled a,.ui-tabs .ui-tabs-nav li.ui-state-processing a{cursor:text}.ui-tabs .ui-tabs-nav li a,.ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tabs .ui-tabs-hide{display:none!important} -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/jquery.ui.resizable.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.8.20 - 2012-04-30 2 | * https://github.com/jquery/jquery-ui 3 | * Includes: jquery.ui.resizable.css 4 | * Copyright (c) 2012 AUTHORS.txt; Licensed MIT */ 5 | .ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px} -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/jquery.ui.slider.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.8.20 - 2012-04-30 2 | * https://github.com/jquery/jquery-ui 3 | * Includes: jquery.ui.slider.css 4 | * Copyright (c) 2012 AUTHORS.txt; Licensed MIT */ 5 | .ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0} -------------------------------------------------------------------------------- /CQRSFramework/Services/ApplicationServices/DiaryItemDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Web.Mvc; 4 | 5 | namespace CQRSFramework.Services.ApplicationServices 6 | { 7 | public class DiaryItemDto 8 | { 9 | [HiddenInput(DisplayValue = false)] 10 | public Guid Id { get; set; } 11 | 12 | [HiddenInput(DisplayValue = false)] 13 | public int Version { get; set; } 14 | 15 | [Required] 16 | public string Title { get; set; } 17 | 18 | [Required] 19 | [DataType(DataType.Date)] 20 | [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] 21 | public DateTime From { get; set; } 22 | 23 | [Required] 24 | [DataType(DataType.Date)] 25 | [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] 26 | public DateTime To { get; set; } 27 | 28 | [DataType(DataType.MultilineText)] 29 | public string Description { get; set; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /CQRSFramework/EventHandlers/DiaryIteamCreatedEventHandler.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Events; 2 | using CQRSFramework.Services.ApplicationServices; 3 | using CQRSFramework.Storage; 4 | 5 | namespace CQRSFramework.EventHandlers 6 | { 7 | // DiaryItemCreatedEvent的事件处理类 8 | public class DiaryIteamCreatedEventHandler : IEventHandler 9 | { 10 | private readonly IStorage _storage; 11 | 12 | public DiaryIteamCreatedEventHandler(IStorage storage) 13 | { 14 | _storage = storage; 15 | } 16 | 17 | public void Handle(DiaryItemCreatedEvent @event) 18 | { 19 | var item = new DiaryItemDto() 20 | { 21 | Id = @event.SourceId, 22 | Description = @event.Description, 23 | From = @event.From, 24 | Title = @event.Title, 25 | To = @event.To, 26 | Version = @event.Version 27 | }; 28 | 29 | // 将领域对象持久化到QueryDatabase中 30 | _storage.Add(item); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /CQRSDemo.Web/App_Start/AuthConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.Web.WebPages.OAuth; 6 | using CQRSDemo.Web.Models; 7 | 8 | namespace CQRSDemo.Web 9 | { 10 | public static class AuthConfig 11 | { 12 | public static void RegisterAuth() 13 | { 14 | // To let users of this site log in using their accounts from other sites such as Microsoft, Facebook, and Twitter, 15 | // you must update this site. For more information visit http://go.microsoft.com/fwlink/?LinkID=252166 16 | 17 | //OAuthWebSecurity.RegisterMicrosoftClient( 18 | // clientId: "", 19 | // clientSecret: ""); 20 | 21 | //OAuthWebSecurity.RegisterTwitterClient( 22 | // consumerKey: "", 23 | // consumerSecret: ""); 24 | 25 | //OAuthWebSecurity.RegisterFacebookClient( 26 | // appId: "", 27 | // appSecret: ""); 28 | 29 | //OAuthWebSecurity.RegisterGoogleClient(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/jquery.ui.accordion.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Accordion 1.8.20 3 | * 4 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 5 | * Licensed under the MIT license. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Accordion#theming 9 | */ 10 | /* IE/Win - Fix animation bug - #4615 */ 11 | .ui-accordion { width: 100%; } 12 | .ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } 13 | .ui-accordion .ui-accordion-li-fix { display: inline; } 14 | .ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } 15 | .ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; } 16 | .ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; } 17 | .ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } 18 | .ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } 19 | .ui-accordion .ui-accordion-content-active { display: block; } 20 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/jquery.ui.dialog.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.8.20 - 2012-04-30 2 | * https://github.com/jquery/jquery-ui 3 | * Includes: jquery.ui.dialog.css 4 | * Copyright (c) 2012 AUTHORS.txt; Licensed MIT */ 5 | .ui-dialog{position:absolute;padding:.2em;width:300px;overflow:hidden}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 16px .1em 0}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:19px;margin:-10px 0 0 0;padding:1px;height:18px}.ui-dialog .ui-dialog-titlebar-close span{display:block;margin:1px}.ui-dialog .ui-dialog-titlebar-close:hover,.ui-dialog .ui-dialog-titlebar-close:focus{padding:0}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto;zoom:1}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin:.5em 0 0 0;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:14px;height:14px;right:3px;bottom:3px}.ui-draggable .ui-dialog-titlebar{cursor:move} -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/jquery.ui.resizable.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Resizable 1.8.20 3 | * 4 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 5 | * Licensed under the MIT license. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Resizable#theming 9 | */ 10 | .ui-resizable { position: relative;} 11 | .ui-resizable-handle { position: absolute;font-size: 0.1px; display: block; } 12 | .ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } 13 | .ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } 14 | .ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } 15 | .ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } 16 | .ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } 17 | .ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } 18 | .ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } 19 | .ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } 20 | .ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;} -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/jquery.ui.slider.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Slider 1.8.20 3 | * 4 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 5 | * Licensed under the MIT license. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Slider#theming 9 | */ 10 | .ui-slider { position: relative; text-align: left; } 11 | .ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } 12 | .ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } 13 | 14 | .ui-slider-horizontal { height: .8em; } 15 | .ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } 16 | .ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } 17 | .ui-slider-horizontal .ui-slider-range-min { left: 0; } 18 | .ui-slider-horizontal .ui-slider-range-max { right: 0; } 19 | 20 | .ui-slider-vertical { width: .8em; height: 100px; } 21 | .ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } 22 | .ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } 23 | .ui-slider-vertical .ui-slider-range-min { bottom: 0; } 24 | .ui-slider-vertical .ui-slider-range-max { top: 0; } -------------------------------------------------------------------------------- /CQRSFramework/Bus/StructureMapEventHandlerFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CQRSFramework.EventHandlers; 5 | using CQRSFramework.Events; 6 | 7 | namespace CQRSFramework.Bus 8 | { 9 | public class StructureMapEventHandlerFactory : IEventHandlerFactory 10 | { 11 | public IEnumerable> GetHandlers() where T : IEvent 12 | { 13 | var handlers = GetHandlerType(); 14 | 15 | var lstHandlers = handlers.Select(handler => 16 | (IEventHandler)ContainerBootstrapper.Container.GetInstance(handler)).ToList(); 17 | return lstHandlers; 18 | } 19 | 20 | private static IEnumerable GetHandlerType() where T : IEvent 21 | { 22 | var handlers = typeof(IEventHandler<>).Assembly.GetExportedTypes() 23 | .Where(x => x.GetInterfaces() 24 | .Any(a => a.IsGenericType && a.GetGenericTypeDefinition() == typeof(IEventHandler<>))) 25 | .Where(h => h.GetInterfaces() 26 | .Any(ii => ii.GetGenericArguments() 27 | .Any(aa => aa == typeof(T)))).ToList(); 28 | 29 | return handlers; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CQRSFramework/CommandHandlers/DeleteItemCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Commands; 2 | using CQRSFramework.Domain; 3 | using CQRSFramework.Repositories; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace CQRSFramework.CommandHandlers 11 | { 12 | public class DeleteItemCommandHandler : ICommandHandler 13 | { 14 | private readonly IDomainRepository _domainRepository; 15 | 16 | public DeleteItemCommandHandler(IDomainRepository domainRepository) 17 | { 18 | _domainRepository = domainRepository; 19 | } 20 | 21 | public void Execute(DeleteItemCommand command) 22 | { 23 | if (command == null) 24 | { 25 | throw new ArgumentNullException("command"); 26 | } 27 | if (_domainRepository == null) 28 | { 29 | throw new InvalidOperationException("domainRepository is not initialized."); 30 | } 31 | 32 | var aggregate = _domainRepository.GetById(command.ID); 33 | aggregate.Delete(); 34 | _domainRepository.Save(aggregate, aggregate.Version); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/jquery.ui.autocomplete.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Autocomplete 1.8.20 3 | * 4 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 5 | * Licensed under the MIT license. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Autocomplete#theming 9 | */ 10 | .ui-autocomplete { position: absolute; cursor: default; } 11 | 12 | /* workarounds */ 13 | * html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ 14 | 15 | /* 16 | * jQuery UI Menu 1.8.20 17 | * 18 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 19 | * Licensed under the MIT license. 20 | * http://jquery.org/license 21 | * 22 | * http://docs.jquery.com/UI/Menu#theming 23 | */ 24 | .ui-menu { 25 | list-style:none; 26 | padding: 2px; 27 | margin: 0; 28 | display:block; 29 | float: left; 30 | } 31 | .ui-menu .ui-menu { 32 | margin-top: -3px; 33 | } 34 | .ui-menu .ui-menu-item { 35 | margin:0; 36 | padding: 0; 37 | zoom: 1; 38 | float: left; 39 | clear: left; 40 | width: 100%; 41 | } 42 | .ui-menu .ui-menu-item a { 43 | text-decoration:none; 44 | display:block; 45 | padding:.2em .4em; 46 | line-height:1.5; 47 | zoom:1; 48 | } 49 | .ui-menu .ui-menu-item a.ui-state-hover, 50 | .ui-menu .ui-menu-item a.ui-state-active { 51 | font-weight: normal; 52 | margin: -1px; 53 | } 54 | -------------------------------------------------------------------------------- /CQRSFramework/Bus/StructureMapCommandHandlerFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CQRSFramework.CommandHandlers; 5 | using CQRSFramework.Commands; 6 | using CQRSFramework.Utils; 7 | 8 | namespace CQRSFramework.Bus 9 | { 10 | public class StructureMapCommandHandlerFactory : ICommandHandlerFactory 11 | { 12 | public IEnumerable> GetHandlers() where T : Command 13 | { 14 | var handlers = GetHandlerTypes().ToList(); 15 | 16 | var cmdHandlers = handlers.Select(handler => 17 | (ICommandHandler)ContainerBootstrapper.Container.GetInstance(handler)).ToList(); 18 | 19 | return cmdHandlers; 20 | } 21 | 22 | private IEnumerable GetHandlerTypes() where T : Command 23 | { 24 | var handlers = typeof(ICommandHandler<>).Assembly.GetExportedTypes() 25 | .Where(x => x.GetInterfaces() 26 | .Any(a => a.IsGenericType && a.GetGenericTypeDefinition() == typeof(ICommandHandler<>))) 27 | .Where(h => h.GetInterfaces() 28 | .Any(ii => ii.GetGenericArguments() 29 | .Any(aa => aa == typeof(T)))).ToList(); 30 | 31 | 32 | return handlers; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /CQRSFramework/CommandHandlers/CreateItemCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Commands; 2 | using CQRSFramework.Domain; 3 | using CQRSFramework.Repositories; 4 | using System; 5 | 6 | namespace CQRSFramework.CommandHandlers 7 | { 8 | // 对CreateItemCommand处理类 9 | public class CreateItemCommandHandler : ICommandHandler 10 | { 11 | private readonly IDomainRepository _domainRepository; 12 | 13 | public CreateItemCommandHandler(IDomainRepository domainRepository) 14 | { 15 | _domainRepository = domainRepository; 16 | } 17 | 18 | // 具体处理逻辑 19 | public void Execute(CreateItemCommand command) 20 | { 21 | if (command == null) 22 | { 23 | throw new ArgumentNullException("command"); 24 | } 25 | if (_domainRepository == null) 26 | { 27 | throw new InvalidOperationException("domainRepository is not initialized."); 28 | } 29 | 30 | var aggregate = new DiaryItem(command.ID, command.Title, command.Description, command.From, command.To) 31 | { 32 | Version = -1 33 | }; 34 | 35 | // 将对应的领域实体进行保存 36 | _domainRepository.Save(aggregate, aggregate.Version); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Web.Debug.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/jquery.ui.dialog.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Dialog 1.8.20 3 | * 4 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 5 | * Licensed under the MIT license. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Dialog#theming 9 | */ 10 | .ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } 11 | .ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; } 12 | .ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; } 13 | .ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } 14 | .ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } 15 | .ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } 16 | .ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } 17 | .ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } 18 | .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } 19 | .ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } 20 | .ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } 21 | .ui-draggable .ui-dialog-titlebar { cursor: move; } 22 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Web.Release.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/jquery.ui.tabs.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Tabs 1.8.20 3 | * 4 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 5 | * Licensed under the MIT license. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Tabs#theming 9 | */ 10 | .ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ 11 | .ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } 12 | .ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } 13 | .ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } 14 | .ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } 15 | .ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } 16 | .ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ 17 | .ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } 18 | .ui-tabs .ui-tabs-hide { display: none !important; } 19 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/jquery.ui.core.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI CSS Framework 1.8.20 3 | * 4 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 5 | * Licensed under the MIT license. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Theming/API 9 | */ 10 | 11 | /* Layout helpers 12 | ----------------------------------*/ 13 | .ui-helper-hidden { display: none; } 14 | .ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); } 15 | .ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } 16 | .ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; } 17 | .ui-helper-clearfix:after { clear: both; } 18 | .ui-helper-clearfix { zoom: 1; } 19 | .ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } 20 | 21 | 22 | /* Interaction Cues 23 | ----------------------------------*/ 24 | .ui-state-disabled { cursor: default !important; } 25 | 26 | 27 | /* Icons 28 | ----------------------------------*/ 29 | 30 | /* states and images */ 31 | .ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } 32 | 33 | 34 | /* Misc visuals 35 | ----------------------------------*/ 36 | 37 | /* Overlays */ 38 | .ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } 39 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CQRSDemo.Web")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("CQRSDemo.Web")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the AggregateRootId of the typelib if this project is exposed to COM 23 | [assembly: Guid("34726d48-bdc0-4ec7-a020-9d7f09b5266e")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @ViewBag.Title 7 | @Styles.Render("~/Content/css") 8 | @Styles.Render("~/Content/themes/base/css") 9 | 10 | @Scripts.Render("~/bundles/modernizr") 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | @Html.ActionLink("Home", "Index", "Home") 22 | 23 | 24 | 25 | 26 | 27 | 28 | @RenderSection("featured", required: false) 29 | 30 | @RenderBody() 31 | 32 | 33 | 40 | 41 | @Scripts.Render("~/bundles/jquery") 42 | @RenderSection("scripts", required: false) 43 | 44 | 45 | -------------------------------------------------------------------------------- /CQRSFramework/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CQRSFramework")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("CQRSFramework")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the AggregateRootId of the typelib if this project is exposed to COM 23 | [assembly: Guid("33cf8e50-c1c1-4455-bba4-62a06d2e8316")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /CQRSFramework/CommandHandlers/ChangeItemCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Commands; 2 | using CQRSFramework.Domain; 3 | using CQRSFramework.Repositories; 4 | using System; 5 | 6 | namespace CQRSFramework.CommandHandlers 7 | { 8 | public class ChangeItemCommandHandler : ICommandHandler 9 | { 10 | private readonly IDomainRepository _domainRepository; 11 | 12 | public ChangeItemCommandHandler(IDomainRepository domainRepository) 13 | { 14 | _domainRepository = domainRepository; 15 | } 16 | 17 | public void Execute(ChangeItemCommand command) 18 | { 19 | if (command == null) 20 | { 21 | throw new ArgumentNullException("command"); 22 | } 23 | if (_domainRepository == null) 24 | { 25 | throw new InvalidOperationException("domainRepository is not initialized."); 26 | } 27 | 28 | var aggregate = _domainRepository.GetById(command.ID); 29 | 30 | if (aggregate.Title != command.Title) 31 | aggregate.ChangeTitle(command.Title); 32 | 33 | if (aggregate.Description != command.Description) 34 | aggregate.ChangeDescription(command.Description); 35 | 36 | if (aggregate.From != command.From) 37 | aggregate.ChangeFrom(command.From); 38 | 39 | if (aggregate.To != command.To) 40 | aggregate.ChangeTo(command.To); 41 | 42 | _domainRepository.Save(aggregate, command.Version); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Views/Home/Add.cshtml: -------------------------------------------------------------------------------- 1 | @model CQRSFramework.Services.ApplicationServices.DiaryItemDto 2 | 3 | @{ 4 | ViewBag.Title = "Add"; 5 | Layout = "~/Views/Shared/_Layout.cshtml"; 6 | } 7 | 8 | Add 9 | 10 | @using (Html.BeginForm()) { 11 | @Html.ValidationSummary(true) 12 | 13 | 14 | DiaryItemAdd 15 | 16 | 17 | @Html.LabelFor(Model => Model.Title) 18 | 19 | 20 | @Html.EditorFor(model => model.Title) 21 | @Html.ValidationMessageFor(model => model.Title) 22 | 23 | 24 | 25 | @Html.LabelFor(model => model.From) 26 | 27 | 28 | @Html.EditorFor(model => model.From) 29 | @Html.ValidationMessageFor(model => model.From) 30 | 31 | 32 | 33 | @Html.LabelFor(model => model.To) 34 | 35 | 36 | @Html.EditorFor(model => model.To) 37 | @Html.ValidationMessageFor(model => model.To) 38 | 39 | 40 | 41 | @Html.LabelFor(model => model.Description) 42 | 43 | 44 | @Html.EditorFor(model => model.Description) 45 | @Html.ValidationMessageFor(model => model.Description) 46 | 47 | 48 | 49 | 50 | 51 | 52 | } 53 | 54 | 55 | @Html.ActionLink("Back to List", "Index") 56 | 57 | 58 | @section Scripts { 59 | @Scripts.Render("~/bundles/jqueryval") 60 | } 61 | -------------------------------------------------------------------------------- /CQRSDemo.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CQRSFramework", "CQRSFramework\CQRSFramework.csproj", "{AEB77F00-52E7-4350-94C4-19C9EC37308D}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CQRSDemo.Web", "CQRSDemo.Web\CQRSDemo.Web.csproj", "{5C953EDB-0596-4321-AF59-8CA10B22E316}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{11338F6B-506C-41ED-8E6F-2CF1AFA7080C}" 9 | ProjectSection(SolutionItems) = preProject 10 | .nuget\NuGet.Config = .nuget\NuGet.Config 11 | .nuget\NuGet.exe = .nuget\NuGet.exe 12 | .nuget\NuGet.targets = .nuget\NuGet.targets 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {AEB77F00-52E7-4350-94C4-19C9EC37308D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {AEB77F00-52E7-4350-94C4-19C9EC37308D}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {AEB77F00-52E7-4350-94C4-19C9EC37308D}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {AEB77F00-52E7-4350-94C4-19C9EC37308D}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {5C953EDB-0596-4321-AF59-8CA10B22E316}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {5C953EDB-0596-4321-AF59-8CA10B22E316}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {5C953EDB-0596-4321-AF59-8CA10B22E316}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {5C953EDB-0596-4321-AF59-8CA10B22E316}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | EndGlobal 34 | -------------------------------------------------------------------------------- /CQRSFramework/Domain/AggregateRoot.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Events; 2 | using CQRSFramework.Utils; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace CQRSFramework.Domain 10 | { 11 | public abstract class AggregateRoot : IEventProvider, IAggregateRoot 12 | { 13 | private readonly List _changes; 14 | 15 | #region IEntity Members 16 | public Guid ID { get; internal set; } 17 | #endregion 18 | 19 | public int Version { get; internal set; } 20 | public int EventVersion { get; protected set; } 21 | 22 | protected AggregateRoot() 23 | { 24 | _changes = new List(); 25 | } 26 | 27 | #region IEventProvider Members 28 | 29 | public void LoadsFromHistory(IEnumerable history) 30 | { 31 | foreach (var e in history) 32 | ApplyChange(e, false); 33 | 34 | Version = history.Last().Version; 35 | EventVersion = Version; 36 | } 37 | 38 | public IEnumerable GetUncommittedChanges() 39 | { 40 | return _changes; 41 | } 42 | #endregion 43 | 44 | public void MarkChangesAsCommitted() 45 | { 46 | _changes.Clear(); 47 | } 48 | 49 | protected void ApplyChange(DomainEvent @event) 50 | { 51 | ApplyChange(@event, true); 52 | } 53 | 54 | private void ApplyChange(DomainEvent @event, bool isNew) 55 | { 56 | dynamic d = this; 57 | 58 | d.Handle(TypeConverter.ChangeTo(@event, @event.GetType())); 59 | if (isNew) 60 | { 61 | _changes.Add(@event); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/jquery.ui.button.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.8.20 - 2012-04-30 2 | * https://github.com/jquery/jquery-ui 3 | * Includes: jquery.ui.button.css 4 | * Copyright (c) 2012 AUTHORS.txt; Licensed MIT */ 5 | .ui-button{display:inline-block;position:relative;padding:0;margin-right:.1em;text-decoration:none!important;cursor:pointer;text-align:center;zoom:1;overflow:visible}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:1.4}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}button.ui-button::-moz-focus-inner{border:0;padding:0} -------------------------------------------------------------------------------- /CQRSDemo.Web/Views/Home/Edit.cshtml: -------------------------------------------------------------------------------- 1 | @model CQRSFramework.Services.ApplicationServices.DiaryItemDto 2 | 3 | @{ 4 | ViewBag.Title = "Add"; 5 | Layout = "~/Views/Shared/_Layout.cshtml"; 6 | } 7 | 8 | Add 9 | 10 | @using (Html.BeginForm()) { 11 | @Html.ValidationSummary(true) 12 | 13 | 14 | DiaryItemAdd 15 | 16 | 17 | @Html.LabelFor(model => model.Title) 18 | 19 | 20 | @Html.EditorFor(Model=>Model.Title) 21 | @Html.ValidationMessageFor(model => model.Title) 22 | 23 | 24 | 25 | @Html.LabelFor(model => model.From) 26 | 27 | 28 | @Html.EditorFor(model => model.From) 29 | @Html.ValidationMessageFor(model => model.From) 30 | 31 | 32 | 33 | @Html.LabelFor(model => model.To) 34 | 35 | 36 | @Html.EditorFor(model => model.To) 37 | @Html.ValidationMessageFor(model => model.To) 38 | 39 | 40 | 41 | @Html.LabelFor(model => model.Description) 42 | 43 | 44 | @Html.EditorFor(model => model.Description) 45 | @Html.ValidationMessageFor(model => model.Description) 46 | 47 | @Html.EditorFor(model => model.Id) 48 | @Html.EditorFor(model => model.Version) 49 | 50 | 51 | 52 | 53 | } 54 | 55 | 56 | @Html.ActionLink("Back to List", "Index") 57 | 58 | 59 | @section Scripts { 60 | @Scripts.Render("~/bundles/jqueryval") 61 | } 62 | -------------------------------------------------------------------------------- /CQRSFramework/Repositories/DomainRepository.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Domain; 2 | using CQRSFramework.Events; 3 | using CQRSFramework.Exceptions; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using CQRSFramework.Events.Storage; 8 | using CQRSFramework.Snapshots; 9 | 10 | namespace CQRSFramework.Repositories 11 | { 12 | // IDomainRepository的实现类 13 | public class DomainRepository : IDomainRepository where T : AggregateRoot, new() 14 | { 15 | private readonly IEventStorage _storage; 16 | 17 | public DomainRepository(IEventStorage storage) 18 | { 19 | _storage = storage; 20 | } 21 | 22 | // 并没有直接对领域实体进行保存,而是先保存领域事件进EventStore,然后在Publish事件到EventBus进行处理 23 | // 然后EventBus把事件分配给对应的事件处理器进行处理,由事件处理器来把领域对象保存到QueryDatabase中 24 | public void Save(AggregateRoot aggregate, int expectedVersion) 25 | { 26 | if (aggregate.GetUncommittedChanges().Any()) 27 | { 28 | _storage.Save(aggregate); 29 | } 30 | } 31 | 32 | public T GetById(Guid id) 33 | { 34 | IEnumerable events; 35 | 36 | // 从快照中查询最近发生一次领域事件 37 | var snapshot = _storage.GetSnapshot(id); 38 | if (snapshot != null) 39 | { 40 | // 如果快照存在,重建过程则从快照事件之后的事件开始重建,而不需要每次从最开始的事件进行重建 41 | events = _storage.GetEvents(id).Where(e => e.Version >= snapshot.Version); 42 | } 43 | else 44 | { 45 | // 从不存在则还是需要从最开始的对象开始重建 46 | events = _storage.GetEvents(id); 47 | } 48 | 49 | var obj = new T(); 50 | if (snapshot != null) 51 | ((ISnapshotOrignator)obj).BuildFromSnapshot(snapshot); // 先应用快照中领域事件 52 | 53 | obj.LoadsFromHistory(events); // 逐个应用领域事件来重建对象 54 | return obj; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Filters/InitializeSimpleMembershipAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity; 3 | using System.Data.Entity.Infrastructure; 4 | using System.Threading; 5 | using System.Web.Mvc; 6 | using WebMatrix.WebData; 7 | using CQRSDemo.Web.Models; 8 | 9 | namespace CQRSDemo.Web.Filters 10 | { 11 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] 12 | public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute 13 | { 14 | private static SimpleMembershipInitializer _initializer; 15 | private static object _initializerLock = new object(); 16 | private static bool _isInitialized; 17 | 18 | public override void OnActionExecuting(ActionExecutingContext filterContext) 19 | { 20 | // Ensure ASP.NET Simple Membership is initialized only once per app start 21 | LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock); 22 | } 23 | 24 | private class SimpleMembershipInitializer 25 | { 26 | public SimpleMembershipInitializer() 27 | { 28 | Database.SetInitializer(null); 29 | 30 | try 31 | { 32 | using (var context = new UsersContext()) 33 | { 34 | if (!context.Database.Exists()) 35 | { 36 | // Create the SimpleMembership database without Entity Framework migration schema 37 | ((IObjectContextAdapter)context).ObjectContext.CreateDatabase(); 38 | } 39 | } 40 | 41 | WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true); 42 | } 43 | catch (Exception ex) 44 | { 45 | throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex); 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /CQRSDemo.Web/App_Start/BundleConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Optimization; 3 | 4 | namespace CQRSDemo.Web 5 | { 6 | public class BundleConfig 7 | { 8 | // For more information on Bundling, visit http://go.microsoft.com/fwlink/?LinkId=254725 9 | public static void RegisterBundles(BundleCollection bundles) 10 | { 11 | bundles.Add(new ScriptBundle("~/bundles/jquery").Include( 12 | "~/Scripts/jquery-{version}.js")); 13 | 14 | bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include( 15 | "~/Scripts/jquery-ui-{version}.js")); 16 | 17 | bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( 18 | "~/Scripts/jquery.unobtrusive*", 19 | "~/Scripts/jquery.validate*")); 20 | 21 | // Use the development version of Modernizr to develop with and learn from. Then, when you're 22 | // ready for production, use the build tool at http://modernizr.com to pick only the tests you need. 23 | bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( 24 | "~/Scripts/modernizr-*")); 25 | 26 | bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css")); 27 | 28 | bundles.Add(new StyleBundle("~/Content/themes/base/css").Include( 29 | "~/Content/themes/base/jquery.ui.core.css", 30 | "~/Content/themes/base/jquery.ui.resizable.css", 31 | "~/Content/themes/base/jquery.ui.selectable.css", 32 | "~/Content/themes/base/jquery.ui.accordion.css", 33 | "~/Content/themes/base/jquery.ui.autocomplete.css", 34 | "~/Content/themes/base/jquery.ui.button.css", 35 | "~/Content/themes/base/jquery.ui.dialog.css", 36 | "~/Content/themes/base/jquery.ui.slider.css", 37 | "~/Content/themes/base/jquery.ui.tabs.css", 38 | "~/Content/themes/base/jquery.ui.datepicker.css", 39 | "~/Content/themes/base/jquery.ui.progressbar.css", 40 | "~/Content/themes/base/jquery.ui.theme.css")); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /CQRSFramework/ServiceLocator.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Bus; 2 | using CQRSFramework.Events.Storage; 3 | using CQRSFramework.Repositories; 4 | using CQRSFramework.Storage; 5 | using StructureMap; 6 | 7 | namespace CQRSFramework 8 | { 9 | // 应用程序初始化操作,将依赖的对象通过依赖注入框架StructureMap进行注入 10 | public sealed class ServiceLocator 11 | { 12 | private static readonly ICommandBus _commandBus; 13 | private static readonly IStorage _queryStorage; 14 | private static readonly bool IsInitialized; 15 | private static readonly object LockThis = new object(); 16 | 17 | static ServiceLocator() 18 | { 19 | if (!IsInitialized) 20 | { 21 | lock (LockThis) 22 | { 23 | // 依赖注入 24 | ContainerBootstrapper.BootstrapStructureMap(); 25 | 26 | _commandBus = ContainerBootstrapper.Container.GetInstance(); 27 | _queryStorage = ContainerBootstrapper.Container.GetInstance(); 28 | IsInitialized = true; 29 | } 30 | } 31 | } 32 | 33 | public static ICommandBus CommandBus 34 | { 35 | get { return _commandBus; } 36 | } 37 | 38 | public static IStorage QueryStorage 39 | { 40 | get { return _queryStorage; } 41 | } 42 | } 43 | 44 | class ContainerBootstrapper 45 | { 46 | private static Container _container; 47 | public static void BootstrapStructureMap() 48 | { 49 | _container = new Container(x => 50 | { 51 | x.For(typeof (IDomainRepository<>)).Singleton().Use(typeof (DomainRepository<>)); 52 | x.For().Singleton().Use(); 53 | x.For().Use(); 54 | x.For().Use(); 55 | x.For().Use(); 56 | x.For().Use(); 57 | x.For().Use(); 58 | }); 59 | } 60 | 61 | public static Container Container 62 | { 63 | get { return _container;} 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework; 2 | using CQRSFramework.Commands; 3 | using CQRSFramework.Exceptions; 4 | using CQRSFramework.Services; 5 | using System; 6 | using System.Web.Mvc; 7 | using CQRSFramework.Services.ApplicationServices; 8 | 9 | namespace CQRSDemo.Web.Controllers 10 | { 11 | public class HomeController : Controller 12 | { 13 | // 查询部分 14 | public ActionResult Index() 15 | { 16 | // 直接获得QueryDatabase对象来查询所有日志 17 | var model = ServiceLocator.QueryStorage.GetItems(); 18 | return View(model); 19 | } 20 | 21 | public ActionResult Add() 22 | { 23 | return View(); 24 | } 25 | 26 | public ActionResult Delete(Guid id) 27 | { 28 | var item = ServiceLocator.QueryStorage.GetById(id); 29 | // 发布DeleteCommand命令 30 | ServiceLocator.CommandBus.Send(new DeleteItemCommand(item.Id, item.Version)); 31 | 32 | return RedirectToAction("Index"); 33 | } 34 | 35 | [HttpPost] 36 | public ActionResult Add(DiaryItemDto item) 37 | { 38 | // 发布CreateItemCommand到CommandBus中 39 | ServiceLocator.CommandBus.Send(new CreateItemCommand(Guid.NewGuid(), item.Title, item.Description, -1, item.From, item.To)); 40 | 41 | return RedirectToAction("Index"); 42 | } 43 | 44 | public ActionResult Edit(Guid id) 45 | { 46 | var item = ServiceLocator.QueryStorage.GetById(id); 47 | var model = new DiaryItemDto() 48 | { 49 | Description = item.Description, 50 | From = item.From, 51 | Id = item.Id, 52 | Title = item.Title, 53 | To = item.To, 54 | Version = item.Version 55 | }; 56 | return View(model); 57 | } 58 | 59 | [HttpPost] 60 | public ActionResult Edit(DiaryItemDto item) 61 | { 62 | try 63 | { 64 | ServiceLocator.CommandBus.Send(new ChangeItemCommand(item.Id, item.Title, item.Description, item.From, item.To, item.Version)); 65 | } 66 | catch (ConcurrencyException err) 67 | { 68 | 69 | ViewBag.error = err.Message; 70 | ModelState.AddModelError("", err.Message); 71 | return View(); 72 | 73 | } 74 | 75 | return RedirectToAction("Index"); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/jquery.ui.button.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Button 1.8.20 3 | * 4 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 5 | * Licensed under the MIT license. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Button#theming 9 | */ 10 | .ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ 11 | .ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ 12 | button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ 13 | .ui-button-icons-only { width: 3.4em; } 14 | button.ui-button-icons-only { width: 3.7em; } 15 | 16 | /*button text element */ 17 | .ui-button .ui-button-text { display: block; line-height: 1.4; } 18 | .ui-button-text-only .ui-button-text { padding: .4em 1em; } 19 | .ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } 20 | .ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } 21 | .ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } 22 | .ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } 23 | /* no icon support for input elements, provide padding by default */ 24 | input.ui-button { padding: .4em 1em; } 25 | 26 | /*button icon element(s) */ 27 | .ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } 28 | .ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } 29 | .ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } 30 | .ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } 31 | .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } 32 | 33 | /*button sets*/ 34 | .ui-buttonset { margin-right: 7px; } 35 | .ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } 36 | 37 | /* workarounds */ 38 | button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ 39 | -------------------------------------------------------------------------------- /CQRSFramework/Domain/DiaryItem.cs: -------------------------------------------------------------------------------- 1 | using CQRSFramework.Events; 2 | using System; 3 | 4 | namespace CQRSFramework.Domain 5 | { 6 | public class DiaryItem : AggregateRoot, 7 | IHandle, 8 | IHandle, 9 | IHandle, 10 | IHandle, 11 | IHandle 12 | { 13 | public string Title { get; set; } 14 | 15 | public DateTime From { get; set; } 16 | public DateTime To { get; set; } 17 | public string Description { get; set; } 18 | 19 | public DiaryItem() 20 | { 21 | } 22 | 23 | public DiaryItem(Guid id, string title, string description, DateTime from, DateTime to) 24 | { 25 | ApplyChange(new DiaryItemCreatedEvent(id, title, description, from, to)); 26 | } 27 | 28 | public void ChangeTitle(string title) 29 | { 30 | ApplyChange(new DiaryItemRenamedEvent(ID, title)); 31 | } 32 | 33 | public void ChangeDescription(string description) 34 | { 35 | ApplyChange(new DiaryItemDescriptionChangedEvent(ID, description)); 36 | } 37 | 38 | public void ChangeFrom(DateTime from) 39 | { 40 | ApplyChange(new DiaryItemFromChangedEvent(ID, from)); 41 | } 42 | 43 | public void ChangeTo(DateTime to) 44 | { 45 | ApplyChange(new DiaryItemToChangedEvent(ID, to)); 46 | } 47 | 48 | public void Delete() 49 | { 50 | ApplyChange(new DiaryItemDeletedEvent(ID)); 51 | } 52 | 53 | public void Handle(DiaryItemDeletedEvent e) 54 | { 55 | 56 | } 57 | 58 | public void Handle(DiaryItemCreatedEvent @event) 59 | { 60 | Title = @event.Title; 61 | From = @event.From; 62 | To = @event.To; 63 | ID = @event.SourceId; 64 | Description = @event.Description; 65 | Version = @event.Version; 66 | } 67 | 68 | public void Handle(DiaryItemRenamedEvent e) 69 | { 70 | Title = e.Title; 71 | } 72 | 73 | public void Handle(DiaryItemFromChangedEvent e) 74 | { 75 | From = e.From; 76 | } 77 | 78 | public void Handle(DiaryItemToChangedEvent e) 79 | { 80 | To = e.To; 81 | } 82 | 83 | public void Handle(DiaryItemDescriptionChangedEvent e) 84 | { 85 | Description = e.Description; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Scripts/jquery.unobtrusive-ajax.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | ** Unobtrusive Ajax support library for jQuery 3 | ** Copyright (C) Microsoft Corporation. All rights reserved. 4 | */ 5 | (function(a){var b="unobtrusiveAjaxClick",g="unobtrusiveValidation";function c(d,b){var a=window,c=(d||"").split(".");while(a&&c.length)a=a[c.shift()];if(typeof a==="function")return a;b.push(d);return Function.constructor.apply(null,b)}function d(a){return a==="GET"||a==="POST"}function f(b,a){!d(a)&&b.setRequestHeader("X-HTTP-Method-Override",a)}function h(c,b,e){var d;if(e.indexOf("application/x-javascript")!==-1)return;d=(c.getAttribute("data-ajax-mode")||"").toUpperCase();a(c.getAttribute("data-ajax-update")).each(function(f,c){var e;switch(d){case"BEFORE":e=c.firstChild;a("").html(b).contents().each(function(){c.insertBefore(this,e)});break;case"AFTER":a("").html(b).contents().each(function(){c.appendChild(this)});break;default:a(c).html(b)}})}function e(b,e){var j,k,g,i;j=b.getAttribute("data-ajax-confirm");if(j&&!window.confirm(j))return;k=a(b.getAttribute("data-ajax-loading"));i=b.getAttribute("data-ajax-loading-duration")||0;a.extend(e,{type:b.getAttribute("data-ajax-method")||undefined,url:b.getAttribute("data-ajax-url")||undefined,beforeSend:function(d){var a;f(d,g);a=c(b.getAttribute("data-ajax-begin"),["xhr"]).apply(this,arguments);a!==false&&k.show(i);return a},complete:function(){k.hide(i);c(b.getAttribute("data-ajax-complete"),["xhr","status"]).apply(this,arguments)},success:function(a,e,d){h(b,a,d.getResponseHeader("Content-Type")||"text/html");c(b.getAttribute("data-ajax-success"),["data","status","xhr"]).apply(this,arguments)},error:c(b.getAttribute("data-ajax-failure"),["xhr","status","error"])});e.data.push({name:"X-Requested-With",value:"XMLHttpRequest"});g=e.type.toUpperCase();if(!d(g)){e.type="POST";e.data.push({name:"X-HTTP-Method-Override",value:g})}a.ajax(e)}function i(c){var b=a(c).data(g);return!b||!b.validate||b.validate()}a("a[data-ajax=true]").live("click",function(a){a.preventDefault();e(this,{url:this.href,type:"GET",data:[]})});a("form[data-ajax=true] input[type=image]").live("click",function(c){var g=c.target.name,d=a(c.target),f=d.parents("form")[0],e=d.offset();a(f).data(b,[{name:g+".x",value:Math.round(c.pageX-e.left)},{name:g+".y",value:Math.round(c.pageY-e.top)}]);setTimeout(function(){a(f).removeData(b)},0)});a("form[data-ajax=true] :submit").live("click",function(c){var e=c.target.name,d=a(c.target).parents("form")[0];a(d).data(b,e?[{name:e,value:c.target.value}]:[]);setTimeout(function(){a(d).removeData(b)},0)});a("form[data-ajax=true]").live("submit",function(d){var c=a(this).data(b)||[];d.preventDefault();if(!i(this))return;e(this,{url:this.action,type:this.method||"GET",data:c.concat(a(this).serializeArray())})})})(jQuery); -------------------------------------------------------------------------------- /CQRSDemo.Web/Views/Web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 40 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /CQRSFramework/Events/Storage/InMemoryEventStorage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CQRSFramework.Bus; 5 | using CQRSFramework.Domain; 6 | using CQRSFramework.Exceptions; 7 | using CQRSFramework.Snapshots; 8 | using CQRSFramework.Utils; 9 | 10 | namespace CQRSFramework.Events.Storage 11 | { 12 | // Event Store的实现,这里保存在内存中,通常是保存到具体的数据库中,如SQL Server、Mongodb等 13 | public class InMemoryEventStorage : IEventStorage 14 | { 15 | private readonly List _events; 16 | private readonly List _snapshots; 17 | 18 | private readonly IEventBus _eventBus; 19 | 20 | public InMemoryEventStorage(IEventBus eventBus) 21 | { 22 | _events = new List(); 23 | _snapshots = new List(); 24 | _eventBus = eventBus; 25 | } 26 | 27 | public IEnumerable GetEvents(Guid aggregateId) 28 | { 29 | var events = _events.Where(p => p.SourceId == aggregateId).Select(p => p); 30 | if (!events.Any()) 31 | { 32 | throw new AggregateNotFoundException(string.Format("Aggregate with AggregateRootId: {0} was not found", aggregateId)); 33 | } 34 | return events; 35 | } 36 | 37 | // 领域事件的保存 38 | public void Save(AggregateRoot aggregate) 39 | { 40 | // 获得对应领域实体未提交的事件 41 | var uncommittedChanges = aggregate.GetUncommittedChanges(); 42 | var version = aggregate.Version; 43 | 44 | 45 | foreach (var @event in uncommittedChanges) 46 | { 47 | version++; 48 | // 没3个事件创建一次快照 49 | if (version > 2) 50 | { 51 | if (version % 3 == 0) 52 | { 53 | var originator = (ISnapshotOrignator)aggregate; 54 | var snapshot = originator.CreateSnapshot(); 55 | snapshot.Version = version; 56 | SaveSnapshot(snapshot); 57 | } 58 | } 59 | 60 | @event.Version = version; 61 | // 保存事件到EventStore中 62 | _events.Add(@event); 63 | } 64 | 65 | // 保存事件完成之后,再将该事件发布到EventBus 做进一步处理 66 | foreach (var @event in uncommittedChanges) 67 | { 68 | var desEvent = TypeConverter.ChangeTo(@event, @event.GetType()); 69 | _eventBus.Publish(desEvent); 70 | } 71 | } 72 | 73 | public T GetSnapshot(Guid aggregateId) where T : BaseSnapshot 74 | { 75 | var memento = _snapshots.Where(m => m.AggregateRootId == aggregateId).Select(m => m).LastOrDefault(); 76 | if (memento != null) 77 | return (T)memento; 78 | return null; 79 | } 80 | 81 | public void SaveSnapshot(ISnapshot snapshot) 82 | { 83 | _snapshots.Add(snapshot); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /CQRSDemo.Web/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Models/AccountModels.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Data.Entity; 6 | using System.Globalization; 7 | using System.Web.Security; 8 | 9 | namespace CQRSDemo.Web.Models 10 | { 11 | public class UsersContext : DbContext 12 | { 13 | public UsersContext() 14 | : base("DefaultConnection") 15 | { 16 | } 17 | 18 | public DbSet UserProfiles { get; set; } 19 | } 20 | 21 | [Table("UserProfile")] 22 | public class UserProfile 23 | { 24 | [Key] 25 | [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)] 26 | public int UserId { get; set; } 27 | public string UserName { get; set; } 28 | } 29 | 30 | public class RegisterExternalLoginModel 31 | { 32 | [Required] 33 | [Display(Name = "User name")] 34 | public string UserName { get; set; } 35 | 36 | public string ExternalLoginData { get; set; } 37 | } 38 | 39 | public class LocalPasswordModel 40 | { 41 | [Required] 42 | [DataType(DataType.Password)] 43 | [Display(Name = "Current password")] 44 | public string OldPassword { get; set; } 45 | 46 | [Required] 47 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 48 | [DataType(DataType.Password)] 49 | [Display(Name = "New password")] 50 | public string NewPassword { get; set; } 51 | 52 | [DataType(DataType.Password)] 53 | [Display(Name = "Confirm new password")] 54 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 55 | public string ConfirmPassword { get; set; } 56 | } 57 | 58 | public class LoginModel 59 | { 60 | [Required] 61 | [Display(Name = "User name")] 62 | public string UserName { get; set; } 63 | 64 | [Required] 65 | [DataType(DataType.Password)] 66 | [Display(Name = "Password")] 67 | public string Password { get; set; } 68 | 69 | [Display(Name = "Remember me?")] 70 | public bool RememberMe { get; set; } 71 | } 72 | 73 | public class RegisterModel 74 | { 75 | [Required] 76 | [Display(Name = "User name")] 77 | public string UserName { get; set; } 78 | 79 | [Required] 80 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 81 | [DataType(DataType.Password)] 82 | [Display(Name = "Password")] 83 | public string Password { get; set; } 84 | 85 | [DataType(DataType.Password)] 86 | [Display(Name = "Confirm password")] 87 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 88 | public string ConfirmPassword { get; set; } 89 | } 90 | 91 | public class ExternalLogin 92 | { 93 | public string Provider { get; set; } 94 | public string ProviderDisplayName { get; set; } 95 | public string ProviderUserId { get; set; } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/jquery.ui.datepicker.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.8.20 - 2012-04-30 2 | * https://github.com/jquery/jquery-ui 3 | * Includes: jquery.ui.datepicker.css 4 | * Copyright (c) 2012 AUTHORS.txt; Licensed MIT */ 5 | .ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month-year{width:100%}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0em}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current{float:right}.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker-cover{display:none;display position:absolute;z-index:-1;filter:mask();top:-4px;left:-4px;width:200px;height:200px} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/jquery.ui.datepicker.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Datepicker 1.8.20 3 | * 4 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 5 | * Licensed under the MIT license. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Datepicker#theming 9 | */ 10 | .ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; } 11 | .ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } 12 | .ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } 13 | .ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } 14 | .ui-datepicker .ui-datepicker-prev { left:2px; } 15 | .ui-datepicker .ui-datepicker-next { right:2px; } 16 | .ui-datepicker .ui-datepicker-prev-hover { left:1px; } 17 | .ui-datepicker .ui-datepicker-next-hover { right:1px; } 18 | .ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } 19 | .ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } 20 | .ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } 21 | .ui-datepicker select.ui-datepicker-month-year {width: 100%;} 22 | .ui-datepicker select.ui-datepicker-month, 23 | .ui-datepicker select.ui-datepicker-year { width: 49%;} 24 | .ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } 25 | .ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } 26 | .ui-datepicker td { border: 0; padding: 1px; } 27 | .ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } 28 | .ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } 29 | .ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } 30 | .ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } 31 | 32 | /* with multiple calendars */ 33 | .ui-datepicker.ui-datepicker-multi { width:auto; } 34 | .ui-datepicker-multi .ui-datepicker-group { float:left; } 35 | .ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } 36 | .ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } 37 | .ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } 38 | .ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } 39 | .ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } 40 | .ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } 41 | .ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } 42 | .ui-datepicker-row-break { clear:both; width:100%; font-size:0em; } 43 | 44 | /* RTL support */ 45 | .ui-datepicker-rtl { direction: rtl; } 46 | .ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } 47 | .ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } 48 | .ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } 49 | .ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } 50 | .ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } 51 | .ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } 52 | .ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } 53 | .ui-datepicker-rtl .ui-datepicker-group { float:right; } 54 | .ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } 55 | .ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } 56 | 57 | /* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ 58 | .ui-datepicker-cover { 59 | display: none; /*sorry for IE5*/ 60 | display/**/: block; /*sorry for IE5*/ 61 | position: absolute; /*must have*/ 62 | z-index: -1; /*must have*/ 63 | filter: mask(); /*must have*/ 64 | top: -4px; /*must have*/ 65 | left: -4px; /*must have*/ 66 | width: 200px; /*must have*/ 67 | height: 200px; /*must have*/ 68 | } -------------------------------------------------------------------------------- /CQRSDemo.Web/Web.config: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Scripts/jquery.validate.unobtrusive.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | ** Unobtrusive validation support library for jQuery and jQuery Validate 3 | ** Copyright (C) Microsoft Corporation. All rights reserved. 4 | */ 5 | (function(a){var d=a.validator,b,e="unobtrusiveValidation";function c(a,b,c){a.rules[b]=c;if(a.message)a.messages[b]=a.message}function j(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function f(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function h(a){return a.substr(0,a.lastIndexOf(".")+1)}function g(a,b){if(a.indexOf("*.")===0)a=a.replace("*.",b);return a}function m(c,d){var b=a(this).find("[data-valmsg-for='"+f(d[0].name)+"']"),e=a.parseJSON(b.attr("data-valmsg-replace"))!==false;b.removeClass("field-validation-valid").addClass("field-validation-error");c.data("unobtrusiveContainer",b);if(e){b.empty();c.removeClass("input-validation-error").appendTo(b)}else c.hide()}function l(e,d){var c=a(this).find("[data-valmsg-summary=true]"),b=c.find("ul");if(b&&b.length&&d.errorList.length){b.empty();c.addClass("validation-summary-errors").removeClass("validation-summary-valid");a.each(d.errorList,function(){a("").html(this.message).appendTo(b)})}}function k(c){var b=c.data("unobtrusiveContainer"),d=a.parseJSON(b.attr("data-valmsg-replace"));if(b){b.addClass("field-validation-valid").removeClass("field-validation-error");c.removeData("unobtrusiveContainer");d&&b.empty()}}function n(){var b=a(this);b.data("validator").resetForm();b.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors");b.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}function i(c){var b=a(c),d=b.data(e),f=a.proxy(n,c);if(!d){d={options:{errorClass:"input-validation-error",errorElement:"span",errorPlacement:a.proxy(m,c),invalidHandler:a.proxy(l,c),messages:{},rules:{},success:a.proxy(k,c)},attachValidation:function(){b.unbind("reset."+e,f).bind("reset."+e,f).validate(this.options)},validate:function(){b.validate();return b.valid()}};b.data(e,d)}return d}d.unobtrusive={adapters:[],parseElement:function(b,h){var d=a(b),f=d.parents("form")[0],c,e,g;if(!f)return;c=i(f);c.options.rules[b.name]=e={};c.options.messages[b.name]=g={};a.each(this.adapters,function(){var c="data-val-"+this.name,i=d.attr(c),h={};if(i!==undefined){c+="-";a.each(this.params,function(){h[this]=d.attr(c+this)});this.adapt({element:b,form:f,message:i,params:h,rules:e,messages:g})}});a.extend(e,{__dummy__:true});!h&&c.attachValidation()},parse:function(b){var c=a(b).parents("form").andSelf().add(a(b).find("form")).filter("form");a(b).find(":input[data-val=true]").each(function(){d.unobtrusive.parseElement(this,true)});c.each(function(){var a=i(this);a&&a.attachValidation()})}};b=d.unobtrusive.adapters;b.add=function(c,a,b){if(!b){b=a;a=[]}this.push({name:c,params:a,adapt:b});return this};b.addBool=function(a,b){return this.add(a,function(d){c(d,b||a,true)})};b.addMinMax=function(e,g,f,a,d,b){return this.add(e,[d||"min",b||"max"],function(b){var e=b.params.min,d=b.params.max;if(e&&d)c(b,a,[e,d]);else if(e)c(b,g,e);else d&&c(b,f,d)})};b.addSingleVal=function(a,b,d){return this.add(a,[b||"val"],function(e){c(e,d||a,e.params[b])})};d.addMethod("__dummy__",function(){return true});d.addMethod("regex",function(b,c,d){var a;if(this.optional(c))return true;a=(new RegExp(d)).exec(b);return a&&a.index===0&&a[0].length===b.length});d.addMethod("nonalphamin",function(c,d,b){var a;if(b){a=c.match(/\W/g);a=a&&a.length>=b}return a});b.addSingleVal("accept","exts").addSingleVal("regex","pattern");b.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");b.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range");b.add("equalto",["other"],function(b){var i=h(b.element.name),j=b.params.other,d=g(j,i),e=a(b.form).find(":input[name='"+f(d)+"']")[0];c(b,"equalTo",e)});b.add("required",function(a){(a.element.tagName.toUpperCase()!=="INPUT"||a.element.type.toUpperCase()!=="CHECKBOX")&&c(a,"required",true)});b.add("remote",["url","type","additionalfields"],function(b){var d={url:b.params.url,type:b.params.type||"GET",data:{}},e=h(b.element.name);a.each(j(b.params.additionalfields||b.element.name),function(i,h){var c=g(h,e);d.data[c]=function(){return a(b.form).find(":input[name='"+f(c)+"']").val()}});c(b,"remote",d)});b.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&c(a,"minlength",a.params.min);a.params.nonalphamin&&c(a,"nonalphamin",a.params.nonalphamin);a.params.regex&&c(a,"regex",a.params.regex)});a(function(){d.unobtrusive.parse(document)})})(jQuery); -------------------------------------------------------------------------------- /CQRSDemo.Web/Scripts/jquery.unobtrusive-ajax.js: -------------------------------------------------------------------------------- 1 | /*! 2 | ** Unobtrusive Ajax support library for jQuery 3 | ** Copyright (C) Microsoft Corporation. All rights reserved. 4 | */ 5 | 6 | /*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */ 7 | /*global window: false, jQuery: false */ 8 | 9 | (function ($) { 10 | var data_click = "unobtrusiveAjaxClick", 11 | data_validation = "unobtrusiveValidation"; 12 | 13 | function getFunction(code, argNames) { 14 | var fn = window, parts = (code || "").split("."); 15 | while (fn && parts.length) { 16 | fn = fn[parts.shift()]; 17 | } 18 | if (typeof (fn) === "function") { 19 | return fn; 20 | } 21 | argNames.push(code); 22 | return Function.constructor.apply(null, argNames); 23 | } 24 | 25 | function isMethodProxySafe(method) { 26 | return method === "GET" || method === "POST"; 27 | } 28 | 29 | function asyncOnBeforeSend(xhr, method) { 30 | if (!isMethodProxySafe(method)) { 31 | xhr.setRequestHeader("X-HTTP-Method-Override", method); 32 | } 33 | } 34 | 35 | function asyncOnSuccess(element, data, contentType) { 36 | var mode; 37 | 38 | if (contentType.indexOf("application/x-javascript") !== -1) { // jQuery already executes JavaScript for us 39 | return; 40 | } 41 | 42 | mode = (element.getAttribute("data-ajax-mode") || "").toUpperCase(); 43 | $(element.getAttribute("data-ajax-update")).each(function (i, update) { 44 | var top; 45 | 46 | switch (mode) { 47 | case "BEFORE": 48 | top = update.firstChild; 49 | $("").html(data).contents().each(function () { 50 | update.insertBefore(this, top); 51 | }); 52 | break; 53 | case "AFTER": 54 | $("").html(data).contents().each(function () { 55 | update.appendChild(this); 56 | }); 57 | break; 58 | default: 59 | $(update).html(data); 60 | break; 61 | } 62 | }); 63 | } 64 | 65 | function asyncRequest(element, options) { 66 | var confirm, loading, method, duration; 67 | 68 | confirm = element.getAttribute("data-ajax-confirm"); 69 | if (confirm && !window.confirm(confirm)) { 70 | return; 71 | } 72 | 73 | loading = $(element.getAttribute("data-ajax-loading")); 74 | duration = element.getAttribute("data-ajax-loading-duration") || 0; 75 | 76 | $.extend(options, { 77 | type: element.getAttribute("data-ajax-method") || undefined, 78 | url: element.getAttribute("data-ajax-url") || undefined, 79 | beforeSend: function (xhr) { 80 | var result; 81 | asyncOnBeforeSend(xhr, method); 82 | result = getFunction(element.getAttribute("data-ajax-begin"), ["xhr"]).apply(this, arguments); 83 | if (result !== false) { 84 | loading.show(duration); 85 | } 86 | return result; 87 | }, 88 | complete: function () { 89 | loading.hide(duration); 90 | getFunction(element.getAttribute("data-ajax-complete"), ["xhr", "status"]).apply(this, arguments); 91 | }, 92 | success: function (data, status, xhr) { 93 | asyncOnSuccess(element, data, xhr.getResponseHeader("Content-Type") || "text/html"); 94 | getFunction(element.getAttribute("data-ajax-success"), ["data", "status", "xhr"]).apply(this, arguments); 95 | }, 96 | error: getFunction(element.getAttribute("data-ajax-failure"), ["xhr", "status", "error"]) 97 | }); 98 | 99 | options.data.push({ name: "X-Requested-With", value: "XMLHttpRequest" }); 100 | 101 | method = options.type.toUpperCase(); 102 | if (!isMethodProxySafe(method)) { 103 | options.type = "POST"; 104 | options.data.push({ name: "X-HTTP-Method-Override", value: method }); 105 | } 106 | 107 | $.ajax(options); 108 | } 109 | 110 | function validate(form) { 111 | var validationInfo = $(form).data(data_validation); 112 | return !validationInfo || !validationInfo.validate || validationInfo.validate(); 113 | } 114 | 115 | $("a[data-ajax=true]").live("click", function (evt) { 116 | evt.preventDefault(); 117 | asyncRequest(this, { 118 | url: this.href, 119 | type: "GET", 120 | data: [] 121 | }); 122 | }); 123 | 124 | $("form[data-ajax=true] input[type=image]").live("click", function (evt) { 125 | var name = evt.target.name, 126 | $target = $(evt.target), 127 | form = $target.parents("form")[0], 128 | offset = $target.offset(); 129 | 130 | $(form).data(data_click, [ 131 | { name: name + ".x", value: Math.round(evt.pageX - offset.left) }, 132 | { name: name + ".y", value: Math.round(evt.pageY - offset.top) } 133 | ]); 134 | 135 | setTimeout(function () { 136 | $(form).removeData(data_click); 137 | }, 0); 138 | }); 139 | 140 | $("form[data-ajax=true] :submit").live("click", function (evt) { 141 | var name = evt.target.name, 142 | form = $(evt.target).parents("form")[0]; 143 | 144 | $(form).data(data_click, name ? [{ name: name, value: evt.target.value }] : []); 145 | 146 | setTimeout(function () { 147 | $(form).removeData(data_click); 148 | }, 0); 149 | }); 150 | 151 | $("form[data-ajax=true]").live("submit", function (evt) { 152 | var clickInfo = $(this).data(data_click) || []; 153 | evt.preventDefault(); 154 | if (!validate(this)) { 155 | return; 156 | } 157 | asyncRequest(this, { 158 | url: this.action, 159 | type: this.method || "GET", 160 | data: clickInfo.concat($(this).serializeArray()) 161 | }); 162 | }); 163 | }(jQuery)); -------------------------------------------------------------------------------- /CQRSFramework/CQRSFramework.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {AEB77F00-52E7-4350-94C4-19C9EC37308D} 8 | Library 9 | Properties 10 | CQRSFramework 11 | CQRSFramework 12 | v4.5 13 | 512 14 | 15 | ..\ 16 | true 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\structuremap.3.1.5.154\lib\net40\StructureMap.dll 38 | 39 | 40 | ..\packages\structuremap.3.1.5.154\lib\net40\StructureMap.Net4.dll 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 120 | 121 | 122 | 123 | 130 | -------------------------------------------------------------------------------- /.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | true 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 31 | 32 | 33 | 34 | 35 | $(SolutionDir).nuget 36 | 37 | 38 | 39 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config 40 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config 41 | 42 | 43 | 44 | $(MSBuildProjectDirectory)\packages.config 45 | $(PackagesProjectConfig) 46 | 47 | 48 | 49 | 50 | $(NuGetToolsPath)\NuGet.exe 51 | @(PackageSource) 52 | 53 | "$(NuGetExePath)" 54 | mono --runtime=v4.0.30319 "$(NuGetExePath)" 55 | 56 | $(TargetDir.Trim('\\')) 57 | 58 | -RequireConsent 59 | -NonInteractive 60 | 61 | "$(SolutionDir) " 62 | "$(SolutionDir)" 63 | 64 | 65 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) 66 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols 67 | 68 | 69 | 70 | RestorePackages; 71 | $(BuildDependsOn); 72 | 73 | 74 | 75 | 76 | $(BuildDependsOn); 77 | BuildPackage; 78 | 79 | 80 | 81 | 82 | 83 | 84 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/themes/base/minified/jquery.ui.theme.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.8.20 - 2012-04-30 2 | * https://github.com/jquery/jquery-ui 3 | * Includes: jquery.ui.theme.css 4 | * Copyright (c) 2012 AUTHORS.txt; Licensed MIT */ 5 | .ui-widget{font-family:Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #aaa;background:#fff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x;color:#222}.ui-widget-content a{color:#222}.ui-widget-header{border:1px solid #aaa;background:#ccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x;color:#222;font-weight:bold}.ui-widget-header a{color:#222}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #d3d3d3;background:#e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#555}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#555;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #999;background:#dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-hover a,.ui-state-hover a:hover{color:#212121;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #aaa;background:#fff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#212121;text-decoration:none}.ui-widget:active{outline:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fcefa1;background:#fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x;color:#cd0a0a}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-icon{width:16px;height:16px;background-image:url(images/ui-icons_222222_256x240.png)}.ui-widget-content .ui-icon{background-image:url(images/ui-icons_222222_256x240.png)}.ui-widget-header .ui-icon{background-image:url(images/ui-icons_222222_256x240.png)}.ui-state-default .ui-icon{background-image:url(images/ui-icons_888888_256x240.png)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(images/ui-icons_454545_256x240.png)}.ui-state-active .ui-icon{background-image:url(images/ui-icons_454545_256x240.png)}.ui-state-highlight .ui-icon{background-image:url(images/ui-icons_2e83ff_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(images/ui-icons_cd0a0a_256x240.png)}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-off{background-position:-96px -144px}.ui-icon-radio-on{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{-moz-border-radius-topleft:4px;-webkit-border-top-left-radius:4px;-khtml-border-top-left-radius:4px;border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{-moz-border-radius-topright:4px;-webkit-border-top-right-radius:4px;-khtml-border-top-right-radius:4px;border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{-moz-border-radius-bottomleft:4px;-webkit-border-bottom-left-radius:4px;-khtml-border-bottom-left-radius:4px;border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{-moz-border-radius-bottomright:4px;-webkit-border-bottom-right-radius:4px;-khtml-border-bottom-right-radius:4px;border-bottom-right-radius:4px}.ui-widget-overlay{background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30);-moz-border-radius:8px;-khtml-border-radius:8px;-webkit-border-radius:8px;border-radius:8px} -------------------------------------------------------------------------------- /CQRSDemo.Web/Content/Site.css: -------------------------------------------------------------------------------- 1 | html { 2 | background-color: #e2e2e2; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | body { 8 | background-color: #fff; 9 | border-top: solid 10px #000; 10 | color: #333; 11 | font-size: .85em; 12 | font-family: "Segoe UI", Verdana, Helvetica, Sans-Serif; 13 | margin: 0; 14 | padding: 0; 15 | } 16 | 17 | a { 18 | color: #333; 19 | outline: none; 20 | padding-left: 3px; 21 | padding-right: 3px; 22 | text-decoration: underline; 23 | } 24 | 25 | a:link, a:visited, 26 | a:active, a:hover { 27 | color: #333; 28 | } 29 | 30 | a:hover { 31 | background-color: #c7d1d6; 32 | } 33 | 34 | header, footer, hgroup, 35 | nav, section { 36 | display: block; 37 | } 38 | 39 | mark { 40 | background-color: #a6dbed; 41 | padding-left: 5px; 42 | padding-right: 5px; 43 | } 44 | 45 | .float-left { 46 | float: left; 47 | } 48 | 49 | .float-right { 50 | float: right; 51 | } 52 | 53 | .clear-fix:after { 54 | content: "."; 55 | clear: both; 56 | display: block; 57 | height: 0; 58 | visibility: hidden; 59 | } 60 | 61 | h1, h2, h3, 62 | h4, h5, h6 { 63 | color: #000; 64 | margin-bottom: 0; 65 | padding-bottom: 0; 66 | } 67 | 68 | h1 { 69 | font-size: 2em; 70 | } 71 | 72 | h2 { 73 | font-size: 1.75em; 74 | } 75 | 76 | h3 { 77 | font-size: 1.2em; 78 | } 79 | 80 | h4 { 81 | font-size: 1.1em; 82 | } 83 | 84 | h5, h6 { 85 | font-size: 1em; 86 | } 87 | 88 | h5 a:link, h5 a:visited, h5 a:active { 89 | padding: 0; 90 | text-decoration: none; 91 | } 92 | 93 | 94 | /* main layout 95 | ----------------------------------------------------------*/ 96 | .content-wrapper { 97 | margin: 0 auto; 98 | max-width: 960px; 99 | } 100 | 101 | #body { 102 | background-color: #efeeef; 103 | clear: both; 104 | padding-bottom: 35px; 105 | } 106 | 107 | .main-content { 108 | background: url("../Images/accent.png") no-repeat; 109 | padding-left: 10px; 110 | padding-top: 30px; 111 | } 112 | 113 | .featured + .main-content { 114 | background: url("../Images/heroAccent.png") no-repeat; 115 | } 116 | 117 | header .content-wrapper { 118 | padding-top: 20px; 119 | } 120 | 121 | footer { 122 | clear: both; 123 | background-color: #e2e2e2; 124 | font-size: .8em; 125 | height: 100px; 126 | } 127 | 128 | 129 | /* site title 130 | ----------------------------------------------------------*/ 131 | .site-title { 132 | color: #c8c8c8; 133 | font-family: Rockwell, Consolas, "Courier New", Courier, monospace; 134 | font-size: 2.3em; 135 | margin: 0; 136 | } 137 | 138 | .site-title a, .site-title a:hover, .site-title a:active { 139 | background: none; 140 | color: #c8c8c8; 141 | outline: none; 142 | text-decoration: none; 143 | } 144 | 145 | 146 | /* login 147 | ----------------------------------------------------------*/ 148 | #login { 149 | display: block; 150 | font-size: .85em; 151 | margin: 0 0 10px; 152 | text-align: right; 153 | } 154 | 155 | #login a { 156 | background-color: #d3dce0; 157 | margin-left: 10px; 158 | margin-right: 3px; 159 | padding: 2px 3px; 160 | text-decoration: none; 161 | } 162 | 163 | #login a.username { 164 | background: none; 165 | margin: 0; 166 | padding: 0; 167 | text-decoration: underline; 168 | } 169 | 170 | #login ul { 171 | margin: 0; 172 | } 173 | 174 | #login li { 175 | display: inline; 176 | list-style: none; 177 | } 178 | 179 | 180 | /* menu 181 | ----------------------------------------------------------*/ 182 | ul#menu { 183 | font-size: 1.3em; 184 | font-weight: 600; 185 | margin: 0 0 5px; 186 | padding: 0; 187 | text-align: right; 188 | } 189 | 190 | ul#menu li { 191 | display: inline; 192 | list-style: none; 193 | padding-left: 15px; 194 | } 195 | 196 | ul#menu li a { 197 | background: none; 198 | color: #999; 199 | text-decoration: none; 200 | } 201 | 202 | ul#menu li a:hover { 203 | color: #333; 204 | text-decoration: none; 205 | } 206 | 207 | 208 | /* page elements 209 | ----------------------------------------------------------*/ 210 | /* featured */ 211 | .featured { 212 | background-color: #fff; 213 | } 214 | 215 | .featured .content-wrapper { 216 | background-color: #7ac0da; 217 | background-image: -ms-linear-gradient(left, #7ac0da 0%, #a4d4e6 100%); 218 | background-image: -o-linear-gradient(left, #7ac0da 0%, #a4d4e6 100%); 219 | background-image: -webkit-gradient(linear, left top, right top, color-stop(0, #7ac0da), color-stop(1, #a4d4e6)); 220 | background-image: -webkit-linear-gradient(left, #7ac0da 0%, #a4d4e6 100%); 221 | background-image: linear-gradient(left, #7ac0da 0%, #a4d4e6 100%); 222 | color: #3e5667; 223 | padding: 20px 40px 30px 40px; 224 | } 225 | 226 | .featured hgroup.title h1, .featured hgroup.title h2 { 227 | color: #fff; 228 | } 229 | 230 | .featured p { 231 | font-size: 1.1em; 232 | } 233 | 234 | /* page titles */ 235 | hgroup.title { 236 | margin-bottom: 10px; 237 | } 238 | 239 | hgroup.title h1, hgroup.title h2 { 240 | display: inline; 241 | } 242 | 243 | hgroup.title h2 { 244 | font-weight: normal; 245 | margin-left: 3px; 246 | } 247 | 248 | /* features */ 249 | section.feature { 250 | width: 300px; 251 | float: left; 252 | padding: 10px; 253 | } 254 | 255 | /* ordered list */ 256 | ol.round { 257 | list-style-type: none; 258 | padding-left: 0; 259 | } 260 | 261 | ol.round li { 262 | margin: 25px 0; 263 | padding-left: 45px; 264 | } 265 | 266 | ol.round li.zero { 267 | background: url("../Images/orderedList0.png") no-repeat; 268 | } 269 | 270 | ol.round li.one { 271 | background: url("../Images/orderedList1.png") no-repeat; 272 | } 273 | 274 | ol.round li.two { 275 | background: url("../Images/orderedList2.png") no-repeat; 276 | } 277 | 278 | ol.round li.three { 279 | background: url("../Images/orderedList3.png") no-repeat; 280 | } 281 | 282 | ol.round li.four { 283 | background: url("../Images/orderedList4.png") no-repeat; 284 | } 285 | 286 | ol.round li.five { 287 | background: url("../Images/orderedList5.png") no-repeat; 288 | } 289 | 290 | ol.round li.six { 291 | background: url("../Images/orderedList6.png") no-repeat; 292 | } 293 | 294 | ol.round li.seven { 295 | background: url("../Images/orderedList7.png") no-repeat; 296 | } 297 | 298 | ol.round li.eight { 299 | background: url("../Images/orderedList8.png") no-repeat; 300 | } 301 | 302 | ol.round li.nine { 303 | background: url("../Images/orderedList9.png") no-repeat; 304 | } 305 | 306 | /* content */ 307 | article { 308 | float: left; 309 | width: 70%; 310 | } 311 | 312 | aside { 313 | float: right; 314 | width: 25%; 315 | } 316 | 317 | aside ul { 318 | list-style: none; 319 | padding: 0; 320 | } 321 | 322 | aside ul li { 323 | background: url("../Images/bullet.png") no-repeat 0 50%; 324 | padding: 2px 0 2px 20px; 325 | } 326 | 327 | .label { 328 | font-weight: 700; 329 | } 330 | 331 | /* login page */ 332 | #loginForm { 333 | border-right: solid 2px #c8c8c8; 334 | float: left; 335 | width: 55%; 336 | } 337 | 338 | #loginForm .validation-error { 339 | display: block; 340 | margin-left: 15px; 341 | } 342 | 343 | #loginForm .validation-summary-errors ul { 344 | margin: 0; 345 | padding: 0; 346 | } 347 | 348 | #loginForm .validation-summary-errors li { 349 | display: inline; 350 | list-style: none; 351 | margin: 0; 352 | } 353 | 354 | #loginForm input { 355 | width: 250px; 356 | } 357 | 358 | #loginForm input[type="checkbox"], 359 | #loginForm input[type="submit"], 360 | #loginForm input[type="button"], 361 | #loginForm button { 362 | width: auto; 363 | } 364 | 365 | #socialLoginForm { 366 | margin-left: 40px; 367 | float: left; 368 | width: 40%; 369 | } 370 | 371 | #socialLoginForm h2 { 372 | margin-bottom: 5px; 373 | } 374 | 375 | #socialLoginList button { 376 | margin-bottom: 12px; 377 | } 378 | 379 | #logoutForm { 380 | display: inline; 381 | } 382 | 383 | /* contact */ 384 | .contact h3 { 385 | font-size: 1.2em; 386 | } 387 | 388 | .contact p { 389 | margin: 5px 0 0 10px; 390 | } 391 | 392 | .contact iframe { 393 | border: 1px solid #333; 394 | margin: 5px 0 0 10px; 395 | } 396 | 397 | /* forms */ 398 | fieldset { 399 | border: none; 400 | margin: 0; 401 | padding: 0; 402 | } 403 | 404 | fieldset legend { 405 | display: none; 406 | } 407 | 408 | fieldset ol { 409 | padding: 0; 410 | list-style: none; 411 | } 412 | 413 | fieldset ol li { 414 | padding-bottom: 5px; 415 | } 416 | 417 | label { 418 | display: block; 419 | font-size: 1.2em; 420 | font-weight: 600; 421 | } 422 | 423 | label.checkbox { 424 | display: inline; 425 | } 426 | 427 | input, textarea { 428 | border: 1px solid #e2e2e2; 429 | background: #fff; 430 | color: #333; 431 | font-size: 1.2em; 432 | margin: 5px 0 6px 0; 433 | padding: 5px; 434 | width: 300px; 435 | } 436 | 437 | textarea { 438 | font-family: inherit; 439 | width: 500px; 440 | } 441 | 442 | input:focus, textarea:focus { 443 | border: 1px solid #7ac0da; 444 | } 445 | 446 | input[type="checkbox"] { 447 | background: transparent; 448 | border: inherit; 449 | width: auto; 450 | } 451 | 452 | input[type="submit"], 453 | input[type="button"], 454 | button { 455 | background-color: #d3dce0; 456 | border: 1px solid #787878; 457 | cursor: pointer; 458 | font-size: 1.2em; 459 | font-weight: 600; 460 | padding: 7px; 461 | margin-right: 8px; 462 | width: auto; 463 | } 464 | 465 | td input[type="submit"], 466 | td input[type="button"], 467 | td button { 468 | font-size: 1em; 469 | padding: 4px; 470 | margin-right: 4px; 471 | } 472 | 473 | /* info and errors */ 474 | .message-info { 475 | border: 1px solid; 476 | clear: both; 477 | padding: 10px 20px; 478 | } 479 | 480 | .message-error { 481 | clear: both; 482 | color: #e80c4d; 483 | font-size: 1.1em; 484 | font-weight: bold; 485 | margin: 20px 0 10px 0; 486 | } 487 | 488 | .message-success { 489 | color: #7ac0da; 490 | font-size: 1.3em; 491 | font-weight: bold; 492 | margin: 20px 0 10px 0; 493 | } 494 | 495 | .error { 496 | color: #e80c4d; 497 | } 498 | 499 | /* styles for validation helpers */ 500 | .field-validation-error { 501 | color: #e80c4d; 502 | font-weight: bold; 503 | } 504 | 505 | .field-validation-valid { 506 | display: none; 507 | } 508 | 509 | input.input-validation-error { 510 | border: 1px solid #e80c4d; 511 | } 512 | 513 | input[type="checkbox"].input-validation-error { 514 | border: 0 none; 515 | } 516 | 517 | .validation-summary-errors { 518 | color: #e80c4d; 519 | font-weight: bold; 520 | font-size: 1.1em; 521 | } 522 | 523 | .validation-summary-valid { 524 | display: none; 525 | } 526 | 527 | 528 | /* tables 529 | ----------------------------------------------------------*/ 530 | table { 531 | border-collapse: collapse; 532 | border-spacing: 0; 533 | margin-top: 0.75em; 534 | border: 0 none; 535 | } 536 | 537 | th { 538 | font-size: 1.2em; 539 | text-align: left; 540 | border: none 0px; 541 | padding-left: 0; 542 | } 543 | 544 | th a { 545 | display: block; 546 | position: relative; 547 | } 548 | 549 | th a:link, th a:visited, th a:active, th a:hover { 550 | color: #333; 551 | font-weight: 600; 552 | text-decoration: none; 553 | padding: 0; 554 | } 555 | 556 | th a:hover { 557 | color: #000; 558 | } 559 | 560 | th.asc a, th.desc a { 561 | margin-right: .75em; 562 | } 563 | 564 | th.asc a:after, th.desc a:after { 565 | display: block; 566 | position: absolute; 567 | right: 0em; 568 | top: 0; 569 | font-size: 0.75em; 570 | } 571 | 572 | th.asc a:after { 573 | content: '▲'; 574 | } 575 | 576 | th.desc a:after { 577 | content: '▼'; 578 | } 579 | 580 | td { 581 | padding: 0.25em 2em 0.25em 0em; 582 | border: 0 none; 583 | } 584 | 585 | tr.pager td { 586 | padding: 0 0.25em 0 0; 587 | } 588 | 589 | 590 | /******************** 591 | * Mobile Styles * 592 | ********************/ 593 | @media only screen and (max-width: 850px) { 594 | 595 | /* header 596 | ----------------------------------------------------------*/ 597 | header .float-left, 598 | header .float-right { 599 | float: none; 600 | } 601 | 602 | /* logo */ 603 | header .site-title { 604 | margin: 10px; 605 | text-align: center; 606 | } 607 | 608 | /* login */ 609 | #login { 610 | font-size: .85em; 611 | margin: 0 0 12px; 612 | text-align: center; 613 | } 614 | 615 | #login ul { 616 | margin: 5px 0; 617 | padding: 0; 618 | } 619 | 620 | #login li { 621 | display: inline; 622 | list-style: none; 623 | margin: 0; 624 | padding: 0; 625 | } 626 | 627 | #login a { 628 | background: none; 629 | color: #999; 630 | font-weight: 600; 631 | margin: 2px; 632 | padding: 0; 633 | } 634 | 635 | #login a:hover { 636 | color: #333; 637 | } 638 | 639 | /* menu */ 640 | nav { 641 | margin-bottom: 5px; 642 | } 643 | 644 | ul#menu { 645 | margin: 0; 646 | padding: 0; 647 | text-align: center; 648 | } 649 | 650 | ul#menu li { 651 | margin: 0; 652 | padding: 0; 653 | } 654 | 655 | 656 | /* main layout 657 | ----------------------------------------------------------*/ 658 | .main-content, 659 | .featured + .main-content { 660 | background-position: 10px 0; 661 | } 662 | 663 | .content-wrapper { 664 | padding-right: 10px; 665 | padding-left: 10px; 666 | } 667 | 668 | .featured .content-wrapper { 669 | padding: 10px; 670 | } 671 | 672 | /* page content */ 673 | article, aside { 674 | float: none; 675 | width: 100%; 676 | } 677 | 678 | /* ordered list */ 679 | ol.round { 680 | list-style-type: none; 681 | padding-left: 0; 682 | } 683 | 684 | ol.round li { 685 | padding-left: 10px; 686 | margin: 25px 0; 687 | } 688 | 689 | ol.round li.zero, 690 | ol.round li.one, 691 | ol.round li.two, 692 | ol.round li.three, 693 | ol.round li.four, 694 | ol.round li.five, 695 | ol.round li.six, 696 | ol.round li.seven, 697 | ol.round li.eight, 698 | ol.round li.nine { 699 | background: none; 700 | } 701 | 702 | /* features */ 703 | section.feature { 704 | float: none; 705 | padding: 10px; 706 | width: auto; 707 | } 708 | 709 | section.feature img { 710 | color: #999; 711 | content: attr(alt); 712 | font-size: 1.5em; 713 | font-weight: 600; 714 | } 715 | 716 | /* forms */ 717 | input { 718 | width: 90%; 719 | } 720 | 721 | /* login page */ 722 | #loginForm { 723 | border-right: none; 724 | float: none; 725 | width: auto; 726 | } 727 | 728 | #loginForm .validation-error { 729 | display: block; 730 | margin-left: 15px; 731 | } 732 | 733 | #socialLoginForm { 734 | margin-left: 0; 735 | float: none; 736 | width: auto; 737 | } 738 | 739 | 740 | /* footer 741 | ----------------------------------------------------------*/ 742 | footer .float-left, 743 | footer .float-right { 744 | float: none; 745 | } 746 | 747 | footer { 748 | text-align: center; 749 | height: auto; 750 | padding: 10px 0; 751 | } 752 | 753 | footer p { 754 | margin: 0; 755 | } 756 | } 757 | -------------------------------------------------------------------------------- /CQRSDemo.Web/Scripts/jquery.validate.unobtrusive.js: -------------------------------------------------------------------------------- 1 | /*! 2 | ** Unobtrusive validation support library for jQuery and jQuery Validate 3 | ** Copyright (C) Microsoft Corporation. All rights reserved. 4 | */ 5 | 6 | /*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */ 7 | /*global document: false, jQuery: false */ 8 | 9 | (function ($) { 10 | var $jQval = $.validator, 11 | adapters, 12 | data_validation = "unobtrusiveValidation"; 13 | 14 | function setValidationValues(options, ruleName, value) { 15 | options.rules[ruleName] = value; 16 | if (options.message) { 17 | options.messages[ruleName] = options.message; 18 | } 19 | } 20 | 21 | function splitAndTrim(value) { 22 | return value.replace(/^\s+|\s+$/g, "").split(/\s*,\s*/g); 23 | } 24 | 25 | function escapeAttributeValue(value) { 26 | // As mentioned on http://api.jquery.com/category/selectors/ 27 | return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1"); 28 | } 29 | 30 | function getModelPrefix(fieldName) { 31 | return fieldName.substr(0, fieldName.lastIndexOf(".") + 1); 32 | } 33 | 34 | function appendModelPrefix(value, prefix) { 35 | if (value.indexOf("*.") === 0) { 36 | value = value.replace("*.", prefix); 37 | } 38 | return value; 39 | } 40 | 41 | function onError(error, inputElement) { // 'this' is the form element 42 | var container = $(this).find("[data-valmsg-for='" + escapeAttributeValue(inputElement[0].name) + "']"), 43 | replace = $.parseJSON(container.attr("data-valmsg-replace")) !== false; 44 | 45 | container.removeClass("field-validation-valid").addClass("field-validation-error"); 46 | error.data("unobtrusiveContainer", container); 47 | 48 | if (replace) { 49 | container.empty(); 50 | error.removeClass("input-validation-error").appendTo(container); 51 | } 52 | else { 53 | error.hide(); 54 | } 55 | } 56 | 57 | function onErrors(event, validator) { // 'this' is the form element 58 | var container = $(this).find("[data-valmsg-summary=true]"), 59 | list = container.find("ul"); 60 | 61 | if (list && list.length && validator.errorList.length) { 62 | list.empty(); 63 | container.addClass("validation-summary-errors").removeClass("validation-summary-valid"); 64 | 65 | $.each(validator.errorList, function () { 66 | $("").html(this.message).appendTo(list); 67 | }); 68 | } 69 | } 70 | 71 | function onSuccess(error) { // 'this' is the form element 72 | var container = error.data("unobtrusiveContainer"), 73 | replace = $.parseJSON(container.attr("data-valmsg-replace")); 74 | 75 | if (container) { 76 | container.addClass("field-validation-valid").removeClass("field-validation-error"); 77 | error.removeData("unobtrusiveContainer"); 78 | 79 | if (replace) { 80 | container.empty(); 81 | } 82 | } 83 | } 84 | 85 | function onReset(event) { // 'this' is the form element 86 | var $form = $(this); 87 | $form.data("validator").resetForm(); 88 | $form.find(".validation-summary-errors") 89 | .addClass("validation-summary-valid") 90 | .removeClass("validation-summary-errors"); 91 | $form.find(".field-validation-error") 92 | .addClass("field-validation-valid") 93 | .removeClass("field-validation-error") 94 | .removeData("unobtrusiveContainer") 95 | .find(">*") // If we were using valmsg-replace, get the underlying error 96 | .removeData("unobtrusiveContainer"); 97 | } 98 | 99 | function validationInfo(form) { 100 | var $form = $(form), 101 | result = $form.data(data_validation), 102 | onResetProxy = $.proxy(onReset, form); 103 | 104 | if (!result) { 105 | result = { 106 | options: { // options structure passed to jQuery Validate's validate() method 107 | errorClass: "input-validation-error", 108 | errorElement: "span", 109 | errorPlacement: $.proxy(onError, form), 110 | invalidHandler: $.proxy(onErrors, form), 111 | messages: {}, 112 | rules: {}, 113 | success: $.proxy(onSuccess, form) 114 | }, 115 | attachValidation: function () { 116 | $form 117 | .unbind("reset." + data_validation, onResetProxy) 118 | .bind("reset." + data_validation, onResetProxy) 119 | .validate(this.options); 120 | }, 121 | validate: function () { // a validation function that is called by unobtrusive Ajax 122 | $form.validate(); 123 | return $form.valid(); 124 | } 125 | }; 126 | $form.data(data_validation, result); 127 | } 128 | 129 | return result; 130 | } 131 | 132 | $jQval.unobtrusive = { 133 | adapters: [], 134 | 135 | parseElement: function (element, skipAttach) { 136 | /// 137 | /// Parses a single HTML element for unobtrusive validation attributes. 138 | /// 139 | /// The HTML element to be parsed. 140 | /// [Optional] true to skip attaching the 141 | /// validation to the form. If parsing just this single element, you should specify true. 142 | /// If parsing several elements, you should specify false, and manually attach the validation 143 | /// to the form when you are finished. The default is false. 144 | var $element = $(element), 145 | form = $element.parents("form")[0], 146 | valInfo, rules, messages; 147 | 148 | if (!form) { // Cannot do client-side validation without a form 149 | return; 150 | } 151 | 152 | valInfo = validationInfo(form); 153 | valInfo.options.rules[element.name] = rules = {}; 154 | valInfo.options.messages[element.name] = messages = {}; 155 | 156 | $.each(this.adapters, function () { 157 | var prefix = "data-val-" + this.name, 158 | message = $element.attr(prefix), 159 | paramValues = {}; 160 | 161 | if (message !== undefined) { // Compare against undefined, because an empty message is legal (and falsy) 162 | prefix += "-"; 163 | 164 | $.each(this.params, function () { 165 | paramValues[this] = $element.attr(prefix + this); 166 | }); 167 | 168 | this.adapt({ 169 | element: element, 170 | form: form, 171 | message: message, 172 | params: paramValues, 173 | rules: rules, 174 | messages: messages 175 | }); 176 | } 177 | }); 178 | 179 | $.extend(rules, { "__dummy__": true }); 180 | 181 | if (!skipAttach) { 182 | valInfo.attachValidation(); 183 | } 184 | }, 185 | 186 | parse: function (selector) { 187 | /// 188 | /// Parses all the HTML elements in the specified selector. It looks for input elements decorated 189 | /// with the [data-val=true] attribute value and enables validation according to the data-val-* 190 | /// attribute values. 191 | /// 192 | /// Any valid jQuery selector. 193 | var $forms = $(selector) 194 | .parents("form") 195 | .andSelf() 196 | .add($(selector).find("form")) 197 | .filter("form"); 198 | 199 | $(selector).find(":input[data-val=true]").each(function () { 200 | $jQval.unobtrusive.parseElement(this, true); 201 | }); 202 | 203 | $forms.each(function () { 204 | var info = validationInfo(this); 205 | if (info) { 206 | info.attachValidation(); 207 | } 208 | }); 209 | } 210 | }; 211 | 212 | adapters = $jQval.unobtrusive.adapters; 213 | 214 | adapters.add = function (adapterName, params, fn) { 215 | /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation. 216 | /// The name of the adapter to be added. This matches the name used 217 | /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). 218 | /// [Optional] An array of parameter names (strings) that will 219 | /// be extracted from the data-val-nnnn-mmmm HTML attributes (where nnnn is the adapter name, and 220 | /// mmmm is the parameter name). 221 | /// The function to call, which adapts the values from the HTML 222 | /// attributes into jQuery Validate rules and/or messages. 223 | /// 224 | if (!fn) { // Called with no params, just a function 225 | fn = params; 226 | params = []; 227 | } 228 | this.push({ name: adapterName, params: params, adapt: fn }); 229 | return this; 230 | }; 231 | 232 | adapters.addBool = function (adapterName, ruleName) { 233 | /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where 234 | /// the jQuery Validate validation rule has no parameter values. 235 | /// The name of the adapter to be added. This matches the name used 236 | /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). 237 | /// [Optional] The name of the jQuery Validate rule. If not provided, the value 238 | /// of adapterName will be used instead. 239 | /// 240 | return this.add(adapterName, function (options) { 241 | setValidationValues(options, ruleName || adapterName, true); 242 | }); 243 | }; 244 | 245 | adapters.addMinMax = function (adapterName, minRuleName, maxRuleName, minMaxRuleName, minAttribute, maxAttribute) { 246 | /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where 247 | /// the jQuery Validate validation has three potential rules (one for min-only, one for max-only, and 248 | /// one for min-and-max). The HTML parameters are expected to be named -min and -max. 249 | /// The name of the adapter to be added. This matches the name used 250 | /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). 251 | /// The name of the jQuery Validate rule to be used when you only 252 | /// have a minimum value. 253 | /// The name of the jQuery Validate rule to be used when you only 254 | /// have a maximum value. 255 | /// The name of the jQuery Validate rule to be used when you 256 | /// have both a minimum and maximum value. 257 | /// [Optional] The name of the HTML attribute that 258 | /// contains the minimum value. The default is "min". 259 | /// [Optional] The name of the HTML attribute that 260 | /// contains the maximum value. The default is "max". 261 | /// 262 | return this.add(adapterName, [minAttribute || "min", maxAttribute || "max"], function (options) { 263 | var min = options.params.min, 264 | max = options.params.max; 265 | 266 | if (min && max) { 267 | setValidationValues(options, minMaxRuleName, [min, max]); 268 | } 269 | else if (min) { 270 | setValidationValues(options, minRuleName, min); 271 | } 272 | else if (max) { 273 | setValidationValues(options, maxRuleName, max); 274 | } 275 | }); 276 | }; 277 | 278 | adapters.addSingleVal = function (adapterName, attribute, ruleName) { 279 | /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where 280 | /// the jQuery Validate validation rule has a single value. 281 | /// The name of the adapter to be added. This matches the name used 282 | /// in the data-val-nnnn HTML attribute(where nnnn is the adapter name). 283 | /// [Optional] The name of the HTML attribute that contains the value. 284 | /// The default is "val". 285 | /// [Optional] The name of the jQuery Validate rule. If not provided, the value 286 | /// of adapterName will be used instead. 287 | /// 288 | return this.add(adapterName, [attribute || "val"], function (options) { 289 | setValidationValues(options, ruleName || adapterName, options.params[attribute]); 290 | }); 291 | }; 292 | 293 | $jQval.addMethod("__dummy__", function (value, element, params) { 294 | return true; 295 | }); 296 | 297 | $jQval.addMethod("regex", function (value, element, params) { 298 | var match; 299 | if (this.optional(element)) { 300 | return true; 301 | } 302 | 303 | match = new RegExp(params).exec(value); 304 | return (match && (match.index === 0) && (match[0].length === value.length)); 305 | }); 306 | 307 | $jQval.addMethod("nonalphamin", function (value, element, nonalphamin) { 308 | var match; 309 | if (nonalphamin) { 310 | match = value.match(/\W/g); 311 | match = match && match.length >= nonalphamin; 312 | } 313 | return match; 314 | }); 315 | 316 | adapters.addSingleVal("accept", "exts").addSingleVal("regex", "pattern"); 317 | adapters.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"); 318 | adapters.addMinMax("length", "minlength", "maxlength", "rangelength").addMinMax("range", "min", "max", "range"); 319 | adapters.add("equalto", ["other"], function (options) { 320 | var prefix = getModelPrefix(options.element.name), 321 | other = options.params.other, 322 | fullOtherName = appendModelPrefix(other, prefix), 323 | element = $(options.form).find(":input[name='" + escapeAttributeValue(fullOtherName) + "']")[0]; 324 | 325 | setValidationValues(options, "equalTo", element); 326 | }); 327 | adapters.add("required", function (options) { 328 | // jQuery Validate equates "required" with "mandatory" for checkbox elements 329 | if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") { 330 | setValidationValues(options, "required", true); 331 | } 332 | }); 333 | adapters.add("remote", ["url", "type", "additionalfields"], function (options) { 334 | var value = { 335 | url: options.params.url, 336 | type: options.params.type || "GET", 337 | data: {} 338 | }, 339 | prefix = getModelPrefix(options.element.name); 340 | 341 | $.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) { 342 | var paramName = appendModelPrefix(fieldName, prefix); 343 | value.data[paramName] = function () { 344 | return $(options.form).find(":input[name='" + escapeAttributeValue(paramName) + "']").val(); 345 | }; 346 | }); 347 | 348 | setValidationValues(options, "remote", value); 349 | }); 350 | adapters.add("password", ["min", "nonalphamin", "regex"], function (options) { 351 | if (options.params.min) { 352 | setValidationValues(options, "minlength", options.params.min); 353 | } 354 | if (options.params.nonalphamin) { 355 | setValidationValues(options, "nonalphamin", options.params.nonalphamin); 356 | } 357 | if (options.params.regex) { 358 | setValidationValues(options, "regex", options.params.regex); 359 | } 360 | }); 361 | 362 | $(function () { 363 | $jQval.unobtrusive.parse(document); 364 | }); 365 | } (jQuery)); --------------------------------------------------------------------------------
49 | 50 |
50 | 51 |
126 | 141 |