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/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/Scripts/_references.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/b53c2bd111b99d31f1867c753c1ff20b1d7fca03/CQRSDemo.Web/Scripts/_references.js
--------------------------------------------------------------------------------
/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));
--------------------------------------------------------------------------------
/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/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));
--------------------------------------------------------------------------------
/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/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 |
52 | }
53 |
54 |
55 | @Html.ActionLink("Back to List", "Index")
56 |
57 |
58 | @section Scripts {
59 | @Scripts.Render("~/bundles/jqueryval")
60 | }
61 |
--------------------------------------------------------------------------------
/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 |
53 | }
54 |
55 |
56 | @Html.ActionLink("Back to List", "Index")
57 |
58 |
59 | @section Scripts {
60 | @Scripts.Render("~/bundles/jqueryval")
61 | }
62 |
--------------------------------------------------------------------------------
/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")
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
24 |
25 |
26 |
27 |
28 | @RenderSection("featured", required: false)
29 |
32 |
33 |
40 |
41 | @Scripts.Render("~/bundles/jquery")
42 | @RenderSection("scripts", required: false)
43 |
44 |
45 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/CQRSDemo.Web/Views/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "~/Views/Shared/_Layout.cshtml";
3 | }
--------------------------------------------------------------------------------
/CQRSDemo.Web/Web.Debug.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
17 |
18 |
29 |
30 |
--------------------------------------------------------------------------------
/CQRSDemo.Web/Web.Release.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
17 |
18 |
19 |
30 |
31 |
--------------------------------------------------------------------------------
/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/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhi5753186/CQRSDemo/b53c2bd111b99d31f1867c753c1ff20b1d7fca03/CQRSDemo.Web/favicon.ico
--------------------------------------------------------------------------------
/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.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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/Events/IHandle.cs:
--------------------------------------------------------------------------------
1 | namespace CQRSFramework.Events
2 | {
3 | public interface IHandle where TEvent : IEvent
4 | {
5 | void Handle(TEvent e);
6 | }
7 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/CQRSFramework/Utils/TypeConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 |
4 | namespace CQRSFramework.Utils
5 | {
6 | public class TypeConverter
7 | {
8 | public static Action