public class DatabindingDebugConverter : IValueConverter { #region IValueConverter Members public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { Debugger.Break(); return value; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { Debugger.Break(); return value; } #endregion IValueConverter Members }
<DataTemplate.Resources> <debug:DatabindingDebugConverter x:Key="databindingDebugConverter"/> </DataTemplate.Resources> <DataGrid ItemsSource="{Binding myViewModel,Converter={StaticResource databindingDebugConverter}}" />
1 - Break Point Hit نمیشود:
در این حالت مقدار myViewModel خالی (null) است و یا اصلا myViewModel در DataContext مربوط به DataGrid وجود ندارد در این صورت همچنین در پنجره Out Put Visual Studio:
System.Windows.Data Error: 35 : BindingExpression path error: ‘X’ property not found ...
2 - Break Point Hit میشود:
در این حالت باید value را Watch کنیم (Shift+F9) تا ببینیم علت Bind نشدن چیست؟ شاید (در این مورد خاص) نوع myViewModel از IEnumerable نباشد ...
در حین بررسی و Debug ، شاید گاهی مسئاله لاینحل به نظر برسد ، ولی به نظر من معمولا با کم و زیاد کردن آدرس (Binding (Path به یکی از دو حالت بالا خواهیم رسید ،
مثلا زمانی که Path به صورت myViewModel.MyProperty.MyInnerPtoperty است ، باید Path را با حالات زیر توسط Converter مذکور تست کنیم:
Binding"{Path=myViewModel.MyProperty.MyInnerPtoperty ,Converter="{StaticResource debugger}}" Binding"{Path=myViewModel.MyProperty,Converter="{StaticResource debugger}}" Binding"{Path=myViewModel,Converter="{StaticResource debugger}}" Binding"{Path=.,Converter="{StaticResource debugger}}"
امیدوارم از Binding تان لذت ببرید.
import svelte from 'rollup-plugin-svelte'; import resolve from 'rollup-plugin-node-resolve'; import commonjs from 'rollup-plugin-commonjs'; import livereload from 'rollup-plugin-livereload'; import { terser } from 'rollup-plugin-terser'; const production = !process.env.ROLLUP_WATCH; export default { input: 'src/main.js', output: { sourcemap: true, format: 'iife', name: 'app', file: 'public/bundle.js' }, plugins: [ svelte({ // enable run-time checks when not in production dev: !production, // we'll extract any component CSS out into // a separate file — better for performance css: css => { css.write('public/bundle.css'); } }), // If you have external dependencies installed from // npm, you'll most likely need these plugins. In // some cases you'll need additional configuration — // consult the documentation for details: // https://github.com/rollup/rollup-plugin-commonjs resolve(), commonjs(), // Watch the `public` directory and refresh the // browser on changes when not in production !production && livereload('public'), // If we're building for production (npm run build // instead of npm run dev), minify production && terser() ], watch: { clearScreen: false } };
package.json :
{ "name": "svelte-app", "version": "1.0.0", "devDependencies": { "npm-run-all": "^4.1.5", "rollup": "^1.10.1", "rollup-plugin-commonjs": "^9.3.4", "rollup-plugin-livereload": "^1.0.0", "rollup-plugin-node-resolve": "^4.2.3", "rollup-plugin-svelte": "^5.0.3", "rollup-plugin-terser": "^4.0.4", "sirv-cli": "^0.4.0", "svelte": "^3.0.0" }, "scripts": { "build": "rollup -c", "autobuild": "rollup -c -w", "dev": "run-p start:dev autobuild", "start": "sirv public", "start:dev": "sirv public --dev" } }
build | برای ساخت و ایجاد خروجیهای برنامه توسط rollup مورد قرار استفاده میگیرد. |
autobuild | مانند build برای ساخت خروجیهای نهایی برنامه استفاده میشود. ولی تفاوتی که دارد پس از هر تغییر در سورس کد برنامه به صورت خودکار build جدیدی پس از اجرای آن گرفته میشود. |
dev | برنامه را درحالت Developer Mode اجرا میکند که برای مشاهده تغییرات به صورت خودکار در browser، بدون نیاز به رفرش صفحه و همینطور عیب یابی برنامه مناسب است. |
start | از طریق sirv که یک وب سرور سبک برای هاست کردن سایتهای استاتیک است، برنامه را هاست میکند. |
start:dev | مانند start است با این تفاوت که برنامه را در حالت Developer Mode هاست میکند که میتواند برای عیب یابی برنامه از آن استفاده کرد؛ چرا که سورس برنامه از طریق source Map قابل دسترس خواهد بود. |
دو پوشه src و public هم برای ما به صورت پیش فرض ایجاد شدهاند که فولدر public فایلهای نهایی تولید شده برنامه ما را شامل میشود و src، دربرگیرنده تمام سورس کدهای برنامه ما میباشد.
<script> export let name; </script> <style> h1 { color: purple; } </style> <h1>Hello {name}!</h1>
src/main.js :
import App from './App.svelte'; const app = new App({ target: document.body, props: { name: 'world' } }); export default app;
<div class="btn-group" data-toggle="buttons-radio"> <button class="btn" type="button">بلی</button> <button class="btn" type="button">خیر</button> </div>
در ادامه قصد داریم یک Editor template و یک Display template مخصوص را جهت تدارک یک چنین دکمههایی، برای مدیریت خواص Boolean ایجاد کنیم. به عبارتی اگر مدل برنامه چنین تعاریفی را داشت:
using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace Mvc4TwitterBootStrapTest.Models { public class User { public int Id { set; get; } [DisplayName("نام")] [Required(ErrorMessage="لطفا نام را تکمیل کنید")] public string Name { set; get; } [DisplayName("نام خانوادگی")] [Required(ErrorMessage = "لطفا نام خانوادگی را تکمیل کنید")] public string LastName { set; get; } [DisplayName("فعال است؟")] [UIHint("BootstrapBoolean")] public bool? IsActive { set; get; } } }
تهیه قالب ادیتور Views\Shared\EditorTemplates\BootstrapBoolean.cshtml
@model bool? @{ var yesIsSelected = Model.HasValue && Model.Value ? "active" : null; var noIsSelected = Model.HasValue && !Model.Value ? "active" : null; var isIndeterminate = !Model.HasValue ? "active" : null; var htmlField = ViewData.TemplateInfo.HtmlFieldPrefix; } @Html.HiddenFor(model => model) <div class="btn-group" data-toggle="buttons-radio"> <button type="button" class="btn btn-info @yesIsSelected bool-@htmlField" onclick="$('#@htmlField').val(true);"> بلی</button> <button type="button" class="btn btn-info @noIsSelected bool-@htmlField" onclick="$('#@htmlField').val(false);"> خیر</button> @if (ViewData.ModelMetadata.IsNullableValueType) { <button type="button" class="btn btn-info @isIndeterminate bool-@htmlField" onclick="$('#@htmlField').val('');"> نامشخص</button> } </div>
نوع اطلاعاتی که این قالب ادیتور پردازش خواهد کرد از نوع nullable bool است. البته مشکلی هم با نوعهای bool معمولی ندارد. در حالت nullable، دکمه سومی را به نام «نامشخص» به مجموعه دکمههای «بلی» و «خیر» اضافه میکند. گاهی از اوقات در فرمهای دریافت اطلاعات نیاز است بررسی کنیم آیا واقعا کاربر اطلاعاتی را انتخاب کرده یا اینکه بدون توجه به فیلدها، بر روی دکمه ارسال کلیک کرده است. در یک چنین حالتی تعریف دکمههای سه وضعیتی Boolean میتواند مفهوم پیدا کند.
در مورد اصول تهیه این قالب در ابتدای مطلب، با کلاسهای btn-group و ویژگی data-toggle آشنا شدید. دقیقا این سه دکمه نیز در اینجا به همین نحو تعریف شدهاند.
در ابتدای نمایش یک View، خصوصا در حالت ویرایش اطلاعات، نیاز است اطلاعات موجود، به دکمههای تعریف شده اعمال شوند. در اینجا برای انتخاب یک دکمه، باید کلاس active به آن نسبت داده شود، که نحوه تدارک آنرا در سه متغیر yesIsSelected، noIsSelected و isIndeterminate ابتدای تعاریف قالب مشاهده میکنید.
سپس یک فیلد مخفی به صفحه اضافه شده است. از این جهت که به کمک jQuery، در حین کلیک بر روی یکی از دکمهها، مقدار آنرا به این فیلد که نهایتا به سرور ارسال خواهد شد، اعمال خواهیم کرد.
تهیه قالب نمایشی Views\Shared\DisplayTemplates\BootstrapBoolean.cshtml
@model bool? @if (Model.HasValue) { if (Model.Value) { <span class="label label-success">بلی</span> } else { <span class="label label-important">خیر</span> } } else { <span class="label label-inverse">نامشخص</span> }
و نهایتا برای استفاده از آن تنها کافی است توسط ویژگی UIHint، نام این قالب، به خاصیت Boolean مدنظر اعمال شود:
[UIHint("BootstrapBoolean")] public bool? IsActive { set; get; }
در بخشهای پیشین ( بخش اول و بخش دوم) به خوبی با اصول و روش مسیریابی (Routing) در AngularJS آشناشدیم. در این بخش میخواهم به برخی جزئیات درباره مسیریابی بپردازم.اولین موضوع، تغییراتی است که از نسخه 1.2 به بعد در روش استفاده از سرویس مسیریابی در AngularJS بوجود آمده است. از نسخه 1.2 سرویس مسیریابی از هسته اصلی AngularJS خارج شد و برای استفاده از امکانات این سرویس باید فایل angular-route.js و یا angular-route.min.js را به صفحه خود بیفزاییم:
<script src="~/Scripts/angular.min.js"></script> <script src="~/Scripts/angular-route.min.js"></script>
var app = angular.module("mainApp", ['ngRoute']);
.when('/controllerAS', { controller: 'testController', controllerAs: 'tCtrl', template: '<div>{{tCtrl.Title}}</div>' })
when(string path, object route)
- routeParams$ برای دسترسی به پارامترهای آمده در آدرس صفحه جاری.
- ()location.path$ جاری به صورت یک رشته.
- ()location.search$ جاری به صورت یک شی.
.when('/controllerAS', { controller: 'testController', controllerAs: 'tCtrl', template: '<div>{{tCtrl.Title}}</div>', caseInsensitiveMatch: true })
$routeProvider .when('/resolveTest', { resolve: { // این وابستگی بلافاصله بازمیگردد person: function () { return { name: "Hamid Saberi", email: "Hamid.Saberi@Gmail.com" } }, // بازمیگرداند promise این وابستگی یک // شدن آن به تاخیر میافتد resolve پس تغییر مسیر تا currentDetails: function ($http) { return $http({ method: 'Get', url: '/current_details' }); }, //میتوانیم از یک وابستگی در وابستگی دیگر استفاده کنیم facebookId: function ($http, currentDetails) { $http({ method: 'GET', url: 'http://facebook.com/api/current_user', params: { email: currentDetails.data.emails[0] } }) }, // بارگذاری فایلهای اسکریپت مورد نیاز fileDeps:function($q, $rootScope){ var deferred = $q.defer(); var dependencies = [ 'controllers/AboutViewController.js', 'directives/some-directive.js' ]; //$Script.js بارگذاری وابستگیها با استفاده از $script(dependencies, function(){ // همه وابستگیها بارگذاری شده اند $rootScope.$apply(function(){ deferred.resolve(); }); }); return deferred.promise; } }, controller: function ($scope, person, currentDetails, facebookId) { this.Person = person; }, controllerAs: 'rtCtrl', template: '<div>{{rtCtrl.Person.name}}</div>', caseInsensitiveMatch: true })
<script type="text/ng-template" id="menu"> {{item.caption}} -- <b>{{level}}</b> <ul ng-if="item.child.length > 0"> <li id="{{item.caption}}" ng-finish-render="doWork()" ng-repeat="item in item.child" ng-include="'menu'" ng-init="level = level + 1"></li> </ul> </script>
<div class="well"> <ul> <li id="{{item.caption}}" ng-repeat="item in data" ng-include="'menu'" ng-init="level = level + 1" ng-finish-render="doWork()"></li> </ul> </div>
$scope.doWork = function(){ console.log($('#item2_1')); } $scope.level = 0; $scope.data = [ { caption:'root', child:[ { caption:'item1', child:[{ caption:'item1_1', child:[{ caption:'item1_1_1' }] },{ caption:'item1_1' }] }, { caption:'item2', child:[{ caption:'item2_1', child:[{ caption:'item2_1_1' }] }] } ] } ];
using System.Web.Mvc; namespace jQueryImpromptu.Controllers { public class HomeController : Controller { [HttpGet] public ActionResult Index() { return View(); } [HttpPost] public ActionResult Index(string postId, string filename) { return Content("ok"); } } }
@{ ViewBag.Title = "Index"; var postUrl = Url.Action(actionName: "Index", controllerName: "Home"); } <h2> Index</h2> <input type="button" id="btnDelete" value="delete" /> <div id="infoDiv"> Some Info .... </div> @section JavaScript { <script type="text/javascript"> $(function () { $('#btnDelete').click(function () { $.prompt("آیا برای حذف اطلاعات موجود اطمینان دارید ؟", { title: 'Title', buttons: { "بله": true, "خیر": false }, focus: 2, submit: function (e, v, m, f) { //debugger; if (!v) { return; } var postId = 10; var fileName = "test.jpg" $.ajax({ type: "POST", url: '@postUrl', data: JSON.stringify({ postId: postId, fileName: fileName }), contentType: "application/json; charset=utf-8", dataType: "json", complete: function (xhr, status) { var data = xhr.responseText; if (status === 'error' || !data || data == "nok") { $.prompt("! حذف فایل با خطا مواجه شده است", { title: 'Title', buttons: { "بستن": true } }); } else { $("#infoDiv").fadeOut("slow").remove(); } } }); } }); }); }); </script> }
ALTER SYSTEM SET recyclebin = ON;
ALTER SESSION SET recyclebin = ON;
ALTER SYSTEM SET recyclebin = OFF; یا ALTER SESSION SET recyclebin = OFF;
Create Table Test (ID int, FirstName varchar(255), LastName Varchar(255))
Drop table Test
FLASHBACK TABLE Test TO BEFORE DROP;
Select * From recyclebin; یا SELECT * FROM USER_RECYCLEBIN;
در واقع ما یک سیستمی داریم که شامل مدلی است از دیتاهای ما و از این مدل برای کوئری گرفتن از دیتابیس استفاده میشود، که البته برای بیشتر پروژههای نرم افزاری، معماری درست و ترجیح داده شدهای هم میباشد.
زمانیکه نیازهای پروژه روز به روز افزورده و پیچیدهتر میشود، مدل CRUD بصورت پیوسته از ارزشش کاسته میشود و از آن سادگی اولیهی در درک و خوانایی آن دور خواهد شد.
ذات CQRS بر آن است که شما مدلهای مختلفی را برای خواندن و نوشتن دیتا داشته باشید. الگوی آن چیزی شبیه به تصویر زیر است
ES از برنامه نویسان میخواهد که مدل سنتی CRUD را فراموش کرده و بجای آن تغییراتی را که روی دیتا صورت گرفته، نیز درج نمایند. اینکار به وسیلهی یک دیتابیس Append-only انجام میشود که به نام Event Store شناخته میشود.
در این معماری ما همهی تغییرات روی دیتا را به صورت Serialize Event ذخیره میکنیم که میتواند دوباره در هر زمانی اجرا شده و current state هر objectی را در اختیار بگذارد.
این روش به ما کمک بزرگی میکند تا وضعیت یک object را در گذشته به راحتی پیدا کنیم و از آن میتوان به غیر از فوایدی که دارد، به عنوان یک Logger نیز استفاده نمود. به دلیل اینکه جزء به جزء تغییرات بر روی state سیستم، در آن ثبت شده است. از آنجاییکه دیتا بصورت serialize ذخیره میشود، بارگزاری آن نیز با سرعت بالایی انجام خواهد شد.
public class Movie : AggregateRoot { public string Title { get; set; } public DateTime ReleaseDate { get; set; } public int RunningTimeMinutes { get; set; } public Movie() { } public Movie(Guid movieId, string title, DateTime releaseDate, int runningTimeMinutes) { //پیاده سازی خواهد شد } }
public class CreateMovieCommand : ICommand { public string Title { get; set; } public DateTime ReleaseDate { get; set; } public int RunningTimeMinutes { get; set; } public CreateMovieCommand(string title, DateTime releaseDate, int runningTime) { Title = title; ReleaseDate = releaseDate; RunningTimeMinutes = runningTime; } }
public class CreateMovieCommandHandler : CommandHandler<CreateMovieCommand> { protected IDomainRepository _repository; public CreateMovieCommandHandler(IDomainRepository repository) { _repository = repository; } public override void Handle(CreateMovieCommand command) { var movie = new Domain.Movie(Guid.NewGuid(), command.Title, command.ReleaseDate, command.RunningTimeMinutes); _repository.Save(movie); } }
public class MovieCreatedEvent : DomainEvent { public Guid MovieId { get { return AggregateRootId; } set { AggregateRootId = value;} } public string Title { get; set; } public DateTime ReleaseDate { get; set; } public int RunningTimeMinutes { get; set; } public MovieCreatedEvent(Guid movieId, string title, DateTime releaseDate, int runningTime) { MovieId = movieId; Title = title; ReleaseDate = releaseDate; RunningTimeMinutes = runningTime; } }
public class Movie : AggregateRoot { public string Title { get; set; } public DateTime ReleaseDate { get; set; } public int RunningTimeMinutes { get; set; } public Movie(Guid movieId, string title, DateTime releaseDate, int runningTimeMinutes) { Apply(new MovieCreatedEvent(Guid.NewGuid(), title, releaseDate, runningTimeMinutes)); } }
public class MovieEventHandler : IHandleDomainEvents<MovieCreatedEvent> { public void Handle(MovieCreatedEvent createdEvent) { using (MoviesContext entities = new MoviesContext()) { entities.Movies.Add(new Movie() { Id = createdEvent.AggregateRootId, Title = createdEvent.Title, ReleaseDate = createdEvent.ReleaseDate, RunningTimeMinutes = createdEvent.RunningTimeMinutes }); entities.SaveChanges(); } } }
protected void OnMovieCreated(MovieCreatedEvent domainEvent) { Id = domainEvent.AggregateRootId; Title = domainEvent.Title; ReleaseDate = domainEvent.ReleaseDate; RunningTimeMinutes = domainEvent.RunningTimeMinutes; }
- ذاتا پیاده سازی این مدل سخت و دشوار است و از آنجاییکه سادگی در پیاده سازی سیستمهای نرم افزاری، یک اصل مهم محسوب میشود، بنابراین استفاده از این مدل محدود میشود به سیستمهای نرم افزاری که مزیتهای گفته شده در قسمت فوق برایشان حیاتی محسوب شود.
- برای پیاده سازی سیستمی با این مدل احتیاج به تیم توسعهای است که با مفاهیم آن کاملا آشنا باشد.
- هر چند امروزه فضای فیزیکی برای ذخیره سازی دیتا ارزان محسوب میشود، اما به هر حال استفاده از این مدل به همراه ES، حجم زیادی از Disk space را خواهد گرفت.
- همانطور که دیدید برای پیاده سازی یک Insert ساده، حجم زیادی کد نوشته شدهاست. بنابراین تولید اینگونه نرم افزارها به زمان بیشتری نیاز دارد.