- Custom Project Template Not Found
- msvsmon.exe crashes when hitting breakpoint in native C++ code
- Search for a folder in solution explorer, then click home or the X in the search box. The view is reset.
- External Tools argument current line is always zero.
- Can't create v3 Function project.
- Access violation reading location 0xFFFFFFFFFFFFFFFF. after updating to VS 2019 Update 16.4.3
- Cannot create function app under 16.4
- MSVC2019 generates AVX-512 instruction in AVX/AVX2 mode
- Bad code generation with rsqrtss (register clobber)
- Visual Studio 2019 Debugger crashes when viewing FastLink callstack.
- C#: Fixed a crash when an attribute constructor is decorated with itself and Nullable Reference Types is enabled.
- Fixed an optimization-analysis bug where we lose track of alias information for arrays of indeterminate length (declared as extern int a[]) when we unroll loops, leading to possible incorrect dead-store removal.
new Vue({ el: '#app', template: '<div>Hello DNT</div>' });
new Vue({ el: '#app', data() { return { blogTitle: 'DNT' } }, render: function (createElement) { return createElement('h1', this.blogTitle) } });
Vue.component('child', { template: '<div>Hello DNT users</div>' });
Vue.component('child', { props: ['text'], template: `<div> {{ text }} </div>` }); new Vue({ el: '#app', data() { return { message: 'Hello DNT!' } } });
<child :text="message"></child>
Vue.component('blogPost', { props: { post: { type: Object, required: true } }, template: `<div> <h1>{{ post.title }}</h1> <p>{{ post.body }}</p> </div>` });
[Vue warn]: Invalid prop: type check failed for prop "post". Expected Object, got String. found in ---> <BlogPost> <Root>
Vue.component('blogPost', { props: { post: { type: Object, required: true, validator: obj => { const titleIsValid = typeof obj.title === 'string'; const bodyIsValid = typeof obj.body === 'string'; const isValid = titleIsValid && bodyIsValid; if (!isValid) { console.warn("prop is not valid"); return false; } return true; } } }, template: `<div> <h1>{{ post.title }}</h1> <p>{{ post.body }}</p> </div>` });
Vue.component('blogPost', { props: { post: { type: Object, validator: obj => { const titleIsValid = typeof obj.title === 'string'; const bodyIsValid = typeof obj.body === 'string'; const isValid = titleIsValid && bodyIsValid; if (!isValid) { console.warn("prop is not valid"); return false; } return true; }, default: function() { return { title: 'Vue is fun!', body: 'Vue is fun..................' } } } }, template: `<div> <h1>{{ post.title }}</h1> <p>{{ post.body }}</p> </div>` });
data: function () { return { stars: 5, hover: 5 } },
var dt = { stars: 5, hover: 5 }; Vue.component('blogPost', { data: function() { return dt; }, props: // as before..., template: `<div class="blog-post"> <h1>{{ post.title }}</h1> <p>{{ post.body }}</p> <div class="star-wrap"> <span v-for="n in 5" class="star" :class="{ full: hover >= n+1 }" @click="stars = n+1" @mouseover="hover = n+1" @mouseout="hover = stars" ></span> </div> </div>` });
برای رفع این مشکل کافی است به اینصورت دیتا را تعریف کنیم:
Vue.component('blogPost', { data: function() { return { stars: 5, hover: 5 } }, props: // as before..., template: // as before });
تغییر دیتا درون کامپوننتها
تا اینجا توانستیم از کامپوننت والد دادههایی را به کامپوننتهای فرزند ارسال کنیم. اکنون میخواهیم قابلیت تغییر دیتای تعریف شدهی درون کامپوننت والد را درون کامپوننتها نیز داشته باشیم:
Vue.component('child', { props: ['message'], methods: { changeName() { this.message = "New Name!..." } }, template: '#child-template' }); new Vue({ el: '#app', data() { return { name: 'DNT!' } } });
تمپلیت کامپوننت فوق نیز به صورت x-template درون DOM تعریف شده است:
<script type="text/x-template" id="child-template"> <div> <p>{{ message }}</p> <button @click="changeName">Change Name</button> </div> </script>
فراخوانی کامپوننت نیز به اینصورت میباشد:
<div id="app"> <child :message="name"></child> </div>
همانطور که مشاهده میکنید، دیتای name را از طریق ویژگی message توانستهایم به کامپوننت child ارسال کنیم. درون تمپلیت آن نیز یک دکمه را برای تغییر مقدار این ویژگی تعریف کردهایم. تغییر این ویژگی نیز یک assignment ساده است. اما اگر بر روی دکمهی Change Name کلیک کنید، هشدار زیر را درون کنسول مشاهده خواهید کرد:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "message" found in ---> <Child> <Root>
دلیل آن نیز مشخص است؛ زیرا با تغییر این ویژگی، کامپوننت والد از وجود تغییرات مطلع نشدهاست و فقط این تغییرات، درون کامپوننت child صورت گرفتهاست. برای اطلاعرسانی کامپوننت والد میتوانیم از یک ایونت ویژه استفاده کنیم و کامپوننت والد را از وجود تغییرات مطلع کنیم:
changeName() { this.message = "New Name!...", this.$emit("change-name", this.message); }
برای تگ child نیز این ایونت را اضافه خواهیم کرد:
<child :message="name" @change-name="name = $event"></child>
در اینحالت با تغییر ویژگی message، مقدار دیتای name نیز بلافاصله تغییر پیدا خواهد کرد.
Slots
Slot یک روش عالی برای جایگزینی محتوای درون یک کامپوننت است. فرض کنید میخواهیم کامپوننتمان به صورت زیر باشد:
<modal> Hello </modal>
در اینحالت باید درون تمپلیت مکان قرارگیری Hello را تعیین کنیم. اینکار را میتوانیم با قرار دادن تگ slot انجام دهیم:
Vue.component('modal', { template: ` ... <div class="modal-body"> <slot></slot> </div> ... ` });
اکنون هر محتوایی که درون تگ modal قرار گیرد، در قسمت slot نمایش داده خواهد شد. این نوع slot به صورت پیشفرض میباشد. در واقع میتوانیم slotها را نیز نامگذاری کنیم. به عنوان مثال یک slot برای عنوان modal، یک slot برای بدنه modal و یک slot دیگر برای فوتر modal تعریف کنیم:
Vue.component('modal', { template: ` <div class="modal fade" id="detailsModal" tabindex="-1" role="dialog" aria-labelledby="detailsModalLabel" aria-hidden="false"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="detailsModalLabel"> <slot name="title"></slot> </h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <slot name="body"></slot> </div> <div class="modal-footer"> <slot name="footer"></slot> </div> </div> </div> </div> ` });
اکنون میتوانیم محتوای مورد نظر را برای قرارگیری درون slotها تعیین کنیم:
<modal> <template slot="title">Title</template> <template slot="body">Lorem ipsum dolor sit amet.</template> <template slot="footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-primary">Save changes</button> </template> </modal>
برای ساخت یک extender کافیست تابع مورد نظر را به عنوان آرگومان به شی ko.extenders پاس دهیم. پارامتر اول این تابع، شیء observable شده مورد نظر و پارامتر دوم آن، شیء option برای انجام یک سری تنظیمات یا فرستادن مقادیر مورد نظر به تابع است. خروجی این تابع نیز میتواند یک شی observable یا حتی یک شی computed/observable نیز باشد.
یک مثال ساده برای extenderها به صورت زیر است:
ko.extenders.logOpt = function(target, option) { target.subscribe(function(newValue) { console.log(option + ": " + newValue); }); return target; };
برای استفاده از این extender کافیست آن را در هنگام تعریف تابع observable برای خواص، به صورت زیر فراخوانی نمایید:
this.firstName = ko.observable("Masoud").extend({logOpt: "my first name"});
my first name : Masoud
پیاده سازی یک extender جهت اعلام هشدار برای مقادیر منفی
برای اینکه هنگام ورود دادهها توسط کاربر، بتوانیم با ورود مقادیر منفی یک هشدار (تغییر رنگ ورودی) اعلام کنیم، میتوان به صورت زیر عمل نمود:
ko.extenders.negativeValueWarn = function (target, option) { target.hasWarning = ko.observable(); function warn(newValue) { if(newValue && newValue.substring) { newValue = parseFloat(newValue); } target.hasWarning(newValue < 0 ? true : false); } warn(target()); target.subscribe(warn); return target; };
یاد آوری : در KO برای انتساب مقدار جدید به خواصی که به صورت observable تعریف شده اند به صورت زیر:
target(NewValue) => فراخوانی به صورت تابع و پاس دادن مقدار جدید به آن
target() => فراخوانی به صورت تابع بدون آرگومان
پیاده سازی یک extender برای انتساب مقادیر Boolean به Radio Button ها
برای اینکه radio buttonها نیز بتوانند فقط با مقادیر Boolean مقدار دهی شوند و از طرفی در هنگام عملیات مقید سازی و ارسال نتایج در قالب شی Json به سرور، بدون هیچ گونه تغییر و محاسبات مقادیر مورد نظر به صورت true/false (از نوع Boolean) باشند میتوان به صورت زیر عمل نمود:
ko.extenders["booleanValue"] = function (target) { target.formattedValue = ko.computed({ read: function () { if (target() === true) return "True"; else if (target() === false) return "False"; }, write: function (newValue) { if (newValue) { if (newValue === "False") target(false); else if (newValue === "True") target(true); } } }); target.formattedValue(target()); return target; };
با فرض اینکه کدهای Html صفحه به صورت زیر است:
<span>Do you want fries with that?</span> <label> <input type="radio" name="question" value="True" data-bind="value: myValue.formattedValue" /> Yes </label> <label> <input type="radio" name="question" value="False" data-bind="value: myValue.formattedValue" /> No </label>
برای استفاده از آن کافیست به صورت زیر عمل نمایید:
this.myValue= ko.observable(false).extend({ booleanValue: null });
آغاز کار با الکترون
فرآیندها (Processes) در الکترون به دو بخش تقسیم میشوند:
یک. فرآیند اصلی (Main Process) که همان فایل جاوااسکریپتی است و توسط main، در فایل package.json مشخص شدهاست .فرآیند اصلی تنها فرآیندی است که قابلیت دسترسی به امکانات گرافیکی سیستم عامل را از قبیل نوتیفیکشن ها، دیالوگها ،Tray و ... دارد. فرآیند اصلی میتواند با استفاده از شیء BrowserWindow که در قسمت قبلی کاربرد آن را مشاهده کردیم، render process را ایجاد کند. با هر بار ایجاد یک نمونه از این شیء، یک Render Process ایجاد میشود.
دو. فرآیند رندر (Render Process): از آنجا که الکترون از کرومیوم استفاده میکند و کرومیوم شامل معماری چند پردازشی است، هر صفحهی وب میتواند پردازش خود را داشته باشد که به آن Render Process میگویند. به طور معمول در مرورگرها، صفحات وب در محیطی به نام SandBox اجرا میشوندکه اجازه دسترسی به منابع بومی را ندارند. ولی از آنجا که الکترون میتواند از Node.js استفاده کند، قابلیت دسترسی به تعاملات سطح پایین سیستم عامل را نیز داراست.
در فرآیند اصلی، پنجرهها توسط BrowserWindow ایجاد میشوند و هر پنجرهای که صفحه وبی را برای خودش باز میکند، شامل Render Process خودش است و هر پنجرهای که کارش خاتمه یابد، فرآیند مربوط به خودش به اتمام میرسد. فرآیند اصلی، همه صفحات وب به همراه Render Process مربوط به خودشان را مدیریت میکند و هر فرآیند رندر، از دیگری مجزا و محافظت شده است و تنها تمرکزش بر روی صفحه وبی است که متعلق به خودش است.
در ابتدا قصد داریم یک منو برای برنامهی خود درست کنیم. برای ساخت منو، راههای متفاوتی وجود دارند که فعلا ما راه استفاده از template را بر میگزینیم که به صورت یک آرایه نوشته میشود. کدهای زیر را در فایل index.js یا هر اسمی که برای آن انتخاب کردهاید بنویسید:
const electron = require('electron'); const {app,dialog,BrowserWindow,Menu,shell} = electron; let win; app.on('ready', function () { win = new BrowserWindow({width: 800, height: 600}); win.loadURL(`file://${__dirname}/index.html`); var app_menu=[ { label:'پرونده', submenu:[ { label:'باز کردن', accelerator:'CmdOrCtrl+O', click:()=>{ } }, { label:'ذخیره', accelerator:'CmdOrCtrl+S', click:()=>{ } } ] }, { label:'سیستم', submenu:[ { label:'درباره ما', click:()=> { shell.openExternal('https://www.dntips.ir'); } }, { label:'خروج', accelerator:'CmdOrCtrl+X', click:()=> { win=null; app.quit(); } } ] } ];
در خطوط بعدی، یک کار اضافهتر را جهت آشنایی بیشتر انجام میدهیم. قصد داریم اگر سیستم عامل مکینتاش بود، نام برنامه هم در ابتدای نوار منو نمایش داده شود. به همین جهت در ادامه خطوط زیر را اضافه میکنیم:
if(process.platform=="darwin") { const app_name=app.getName(); app_menu.unshift({ label:app_name }) }
ویندوز | win32 حتی اگر 64 بیتی باشد. |
لینوکس | linux |
مک | darwin |
فری بی اس دی | freebsd |
سولاریس | sunos |
دستو shell در بالا به شما اجازه میدهد با محیط دسکتاپ، یکپارچگی خود
را حفظ کنید و دستوراتی از قبیل باز کردن url، باز کردن یک مسیر دایرکتوری،
باز کردن یک فایل، انتقال فایل به سطل آشغال یا بازیافت و صدای بوق سیستم
(بیپ) را به شما میدهد. مستندات این شیء را در اینجا مطالعه فرمایید.
{ label:'خروج', accelerator:'CmdOrCtrl+X', role:'close' }
در انتها با اجرای دو دستور زیر، منو ساخته میشود:
var menu=Menu.buildFromTemplate(app_menu); Menu.setApplicationMenu(menu);
حال قصد داریم برای زیرمنوی «باز کردن فایل» یک دیالوگ open درخواست کنیم. برای این کار از شیء dialog استفاده میکنیم. پس خطوط زیر را به رویداد کلیک این زیرمنو اضافه میکنیم:
dialog.showOpenDialog({ title:'باز کردن فایل متنی', properties: [ 'openFile']//[ 'openFile', 'openDirectory', 'multiSelections' ] ,filters:[ {name:'فایلهای نوشتاری' , extensions:['txt','text']}, {name:'جهت تست' , extensions:['doc','docx']} ] }, (filename)=>{ if(filename===undefined) return; dialog.showMessageBox({title:'پیام اطلاعاتی',type:"info",buttons:['تایید'],message:`the name of file is [${filename}]`}); });
برای قسمت ذخیره هم کد زیر را مینویسیم:
dialog.showSaveDialog({ title:'باز کردن فایل متنی', properties: [ 'openFile']//[ 'openFile', 'openDirectory', 'multiSelections' ] ,filters:[ {name:'فایلهای نوشتاری' , extensions:['txt','text']} ] }, (filename)=>{ if(filename===undefined) return; });
حال بهتر است این دیالوگهای جاری را هدفمند کنیم و بتوانیم فایلهای متنی را به کاربر نمایش دهیم، یا آنها را ذخیره کنیم. به همین علت فایل html زیر را نوشته و طبق دستوری که در مقاله «آشنایی با الکترون» فرا گرفتیم، آن را نمایش میدهیم:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> Fie Content:<br/> <textarea id="TextFile" cols="100" rows="50"></textarea> </body> </html>
کاری که میخواهیم انجام دهیم این است که فایل متنی را باز کرده و محتوای آن را در کادر متنی نشان دهیم و موقع ذخیره نیز محتوای نوشته شده در کادر متنی را در فایلی ذخیره کنیم. از آنجا که main Process به المانهای DOM یا Render Process دسترسی ندارد، باید از طریقی، ارتباط آن را برقرار کنیم. یکی از راههای برقراری این ارتباط، IPC است. IPC در واقع یک فرستنده و یک شنونده است که هر کدام در یک سمت قرار گرفته اند. فرستنده پیام را تحت یک عنوان ارسال میکند و شنونده منتظر دریافت پیامی تحت همان عنوان میماند و پیام دریافتی را پاسخ میدهد. در این مقاله، ما فقط قسمتی از این نوع ارتباطات را بررسی میکنیم.
در نتیجه محتوای callback کدهای دیالوگ open و save را به شکل زیر تغییر میدهیم:
Open
dialog.showOpenDialog({ title:'باز کردن فایل متنی', properties: [ 'openFile']//[ 'openFile', 'openDirectory', 'multiSelections' ] ,filters:[ {name:'فایلهای نوشتاری' , extensions:['txt','text']}, {name:'جهت تست' , extensions:['doc','docx']} ] }, (filename)=>{ if(filename===undefined) return; win.webContents.send('openFile',filename); // dialog.showMessageBox({title:'پیام اطلاعاتی',type:"info",buttons:['تایید'],message:`the name of file is [${filename}]`}); });
dialog.showSaveDialog({ title:'باز کردن فایل متنی', properties: [ 'openFile']//[ 'openFile', 'openDirectory', 'multiSelections' ] ,filters:[ {name:'فایلهای نوشتاری' , extensions:['txt','text']} ] }, (filename)=>{ if(filename===undefined) return; win.webContents.send('saveFile',filename); });
برای ایجاد شنونده هم کد زیر را به فایل index.html اضافه میکنیم:
<script> const {ipcRenderer} = require('electron'); var fs=require('fs'); ipcRenderer.on('openFile', (event, arg) => { var content= fs.readFileSync(String(arg),'utf8'); document.getElementById("TextFile").value=content; }); ipcRenderer.on('saveFile', (event, arg) =>{ var content=document.getElementById("TextFile").value; fs.writeFileSync(String(arg),content,'utf8'); alert('ذخیره شد'); }); </script>
در اینجا شوندههایی را از نوع ipcRenderer ایجاد میکنیم و با استفاده از متد on، به پیامهایی تحت عنوانهای مشخص شده گوش فرا میدهیم. پیامهای ارسالی را که حاوی آدرس فایل میباشند، به شیءای که از نوع fs میباشد، میدهند و آنها را میخوانند یا مینویسند. خواندن و نوشتن فایل، به صورت همزمان صورت میگیرد. ولی اگر دوست دارید که به صورت غیر همزمان پیامی را بخوانید یا بنویسید، باید عبارت Sync را از نام متدها حذف کنید و یک callback را به عنوان پارامتر دوم قرار دهید و محتوای آن را از طریق نوشتن یک پارامتر در سازنده دریافت کنید.
فایلهای پروژه
شما برای این کار باید چند مرحله را انجام دهید:
مرحلهی اول: یک پروژهی جدید بسازید
نکته: برای اینکار میتوانید پروژه را از نوع ASP.NET 5 Web Application نیز انتخاب نمایید و مراحل کوتاهتری را طی نمایید. اما راه اندازی دستی قسمتهای مختلف پروژه برای یکبار، به درک بهتر ساختار آن کمک زیادی میکند. از طرفی کار کردن بر روی یک پروژهی تمیز و خالی، برای انتقال دادههای مورد نیاز از یک پروژهی دیگر، از بروز خطاهای پیش بینی نشده و تداخلهای احتمالی نیز جلوگیری میکند.
مرحلهی دوم: اعمال تنظیمات جهت
استفادهی از MVC
همان طور که مشاهده میکنید در این پروژه دیگر خبری از web.config نیست. اما نگران نباشید، امکان اعمال تنظیمات، باز هم وجود دارد و فقط به فایلهای json منتقل شدهاند که project.json هم یکی از آنهاست. برای استفادهی از Microsoft.AspNet.Mvc فقط کافی است فایل project.json را باز کنید و در قسمت "dependencies" پکیج "Microsoft.AspNet.Mvc" را به آن اضافه نمایید تا به صورت خودکار دانلود و به پروژه اضافه گردد.
"dependencies": { "Microsoft.AspNet.Server.IIS": "1.0.0-beta5", "Microsoft.AspNet.Server.WebListener": "1.0.0-beta5", "Microsoft.AspNet.Mvc": "6.0.0-beta5" },
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } public void Configure(IApplicationBuilder app) { app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
<h1>My First MVC6 Website!</h1>
برنامه را اجرا کنید تا خروجی مورد انتظار را مشاهده نمایید.
مرحلهی سوم: انتقال فایلهای پروژهی قبلی به پروژهی جدید
Viewهای مربوط به این controller را نیز به پوشهی Views/Home منتقل نمایید و پروژه را بار دیگر اجرا نمایید. حال باید نمایی از محتوای این فایلها بدون اعمال استایلها را مشاهده نمایید.
همان طور که میدانیم MVC 5 برای استایل صفحات خود از bootstrap استفاده میکند و فایلهای مورد نیاز آن در پوشههای Content و Script -که در root سایت موجود هستند- قرار دارند و نیز ارجاع به این فایلها در Layout.cshtml_ قرار دارد. اما در MVC 6 قضیه کمی متفاوت و البته بهتر شده است. در MVC 6 تمام فایلهای client-side شامل css و js در پوشهی wwwroot قرار دارند و ما میتواینم فایلهای bootstrap و غیره را از پروژهی خود، به این مکان کپی نماییم. ولی روش بهتر، استفاده از ابزارهای bower و gulp برای این کار است. همان طور که میدانید bower یک package manager برای نصب، به روزرسانی و مدیریت کتابخانههای سمت کلاینت و gulp نیز یک task runner برای انجام کارهای مختلف از قبیل script minification و ... در سمت کلاینت است. gulp و bower به طور تو کار در ویژوال 2015 پشتیبانی میشوند و حتی اگر پروژهی خود را از نوع ASP.NET 5 Web Application انتخاب کرده باشید، به صورت پیش فرض از آنها استفاده میکند.
در اینجا برای استفاده، ابتدا یک فایل از نوع Bower JSON Configuration را به root پروژه اضافه کرده و آن را bower.json بنامید و در خاصیت "dependencies" آن bootstrap, jquery, jquery-validation, jquery-validation-unobtrusive را اضافه نمایید.
نکته: من در این قسمت، در restore کردن پکیجها با استفاده از bower، با خطای زیر مواجه شدم:
visual 2015 ECMDERR Failed to execute "git ls-remote --tags --heads git://github.com/jquery/jquery.git", exit code of #-532462766
من از نسخهی Visual Studio 2015 Update 1 CTP استفاده میکنم، ولی ظاهرا این مشکل در نسخههای دیگر هم وجود دارد و فایل bower.cmd ویژوال به درستی کار نمیکند. من برای حل این مشکل، ابتدا git را نصب کردم و در تنظیمات bower، مسیر پیش فرض ویژال رو به مسیر نصب git تغییر دادم. یعنی در پروژه بر روی پوشهی Bower کلیک راست و configure external tools را انتخاب کردم و تیک $(DevEnvDir)\Extensions\Microsoft\Web Tools\External\git را برداشته و در عوض مسیر پیش فرض خودم یعنی C:\Program Files\Git\bin را اضافه کردم.
اگر راه درست و اصولیتری برای حل این مشکل وجود دارد ممنون میشوم دوستان راهنمایی بفرمایند.
خب بعد از اضافه کردن خاصیت dependencies و پکیجهای مورد نیاز، خاصیت exportsOverride را نیز مانند نمونه به فایل bower.json اضافه نمایید.
{ "name": "ASP.NET", "private": true, "dependencies": { "bootstrap": "3.3.6", "jquery": "2.1.4", "jquery-validation": "1.14.0", "jquery-validation-unobtrusive": "3.2.4" }, "exportsOverride": { "bootstrap": { "js": "dist/js/*.*", "css": "dist/css/*.*", "fonts": "dist/fonts/*.*" }, "jquery": { "": "jquery.{js,min.js,min.map}" }, "jquery-validation": { "": "jquery.validate.js" }, "jquery-validation-unobtrusive": { "": "jquery.validate.unobtrusive.{js,min.js}" } } }
"devDependencies": { "gulp": "3.9.0", "rimraf": "2.4.4", "gulp-concat": "2.6.0" }
var gulp = require('gulp'); var rimraf = require('rimraf'); var paths = { bower: "./bower_components/", lib: "./wwwroot/lib/" }; gulp.task('clean', function (callback) { rimraf(paths.lib, callback); }); gulp.task('default', ['clean'], function () { var bower = { "bootstrap": "bootstrap/dist/**/*.{js,map,css,ttf,svg,woff,eot}", "jquery": "jquery/jquery*.{js,map}", "jquery-validation": "jquery-validation/jquery.validate.js", "jquery-validation-unobtrusive": "jquery-validation-unobtrusive/jquery.validate.unobtrusive.js" }; for (var destinationDir in bower) { gulp.src(paths.bower + bower[destinationDir]) .pipe(gulp.dest(paths.lib + destinationDir)); } });
اگر پردازش فوق با موفقیت و بدون خطا انجام شود، میتوانید پکیجهای ایجاد شده را در مسیر wwwroot/lib، مشاهده نمایید.
مرحلهی چهارم: ویرایش برخی از view ها
حال که پکیجهای مورد نیاز پروژه، در پوشهی wwwroot قرار گرفتند، باید view هایی که ارجاعی را به این فایلها دارند، نیز ویرایش نماییم. یکی از این فایلها Layout.cshtml_ است که در مسیر Views/Shared قرار دارد. این فایل را باز کرده و به جای متد ()Styles .Render از عنصر <link> برای لود کردن استایلهای بوت استرپ و غیره استفاده نمایید.
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" /> <link rel="stylesheet" href="~/css/site.css" />
<script src="~/lib/jquery/dist/jquery.js"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
مرحلهی پنجم(اختیاری): جایگزین کردن متدهای Html helper با ساختار Tag
Helper
یکی از ویژگیهای جالب و مفید MVC 6 ساختار TagHalper ها هستند که در واقع جایگزینی برای متدهای HtmlHelper و عملکردی مشابه به آنها دارند. البته استفاده از این ویژگی اجباری نیست ولی اگر تعداد ویوهای شما زیاد نیست و خواهان استفادهی از این قابلیت در پروژهی خود هستید، تنها کاری که باید انجام دهید، پیدا کردن HtmlHelperها و جایگزینی آنها به صورت زیر میباشد:
@Html.TextBoxFor(model => model.Name, new { style = "width: 100px" })
<input asp-for="Name" style="width: 100px" />
نتیجه گیری
همان طور ملاحظه نمودید انتقال پروژه از ASP.NET MVC 5 به ASP.NET MVC 6 شامل انجام چند مرحله است و دشواری خاصی ندارد. عمدهی این تغییرات و پیچیدگیها هم مربوط به انتقال فایلهای client-side و نحوهی کار با ابزارهای مدیریت پکیج میشود و البته تنظیماتی که در این بین باید انجام شوند. البته قسمتهای دیگری مانند تنظیمات bundling و connection string نیز با MVC 5 تفاوت هایی دارد و کار با آنها نیز بسیار ساده میباشد.
دو هدف در این مثال مد نظر بود:
الف) تا پایان عملیات اعتبار سنجی توسط عملیات Ajax ایی صبر شود و فرم به یکباره به سرور ارسال نگردد.
ب) اگر فرم اعتبار سنجی نشد، به سرور submit نشود.
$("#JQGrid1").jqGrid({ url: "/manager/Products/OnProductDataRequested", editurl: '/manager/Products/EditProductData', mtype: "GET", datatype: "json", page: 1, sortname: 'Priority', sortorder: "desc", viewrecords: true, jsonReader: { id: "Id" }, prmNames: { id: "Id" }, colNames: ["Id","خلاصه","توضیح خلاصه","قیمت","قیمت با تخفیف","درصد کارمزد سایت","آستانه هشدار موجودی","محتوا", "عنوان","فروشگاه","گروه","تعداد موجودی", "اولویت","محصولات مکمل","تصاویر","مقادیر مشخصه محصول","قیمت","نظرات", "فعال","ویژه","موجود","برند","عملیات درجا","عملیات کامل"], colModel: [ { key: true, width: 50, name: "Id", hidden: true, search: false }, { editable: true, width: 10, name: "Abstract", search: true, stype: "text", editable: true, hidden: true, editrules: { edithidden: true } }, { editable: true, width: 10, name: "AbstractDescription", search: true, stype: "text", editable: true, hidden: true, editrules: { edithidden: true } }, { editable: true, width: 10, name: "Value", search: true, stype: "text", editable: true, hidden: true, editrules: { edithidden: true } }, { editable: true, width: 10, name: "Discount", search: true, stype: "text", editable: true, hidden: true, editrules: { edithidden: true } }, { editable: true, width: 10, name: "SiteWagePercentage", search: true, stype: "text", editable: true, hidden: true, editrules: { edithidden: true } }, { editable: true, width: 10, name: "InventoryAlertLimit", search: true, stype: "text", editable: true, hidden: true, editrules: { edithidden: true } }, { editable: true, width: 10, name: "Context", search: true, stype: "text", editable: true, hidden: true, editrules: { edithidden: true } }, { editable: true, width: 150, name: "Title", search: true, stype: "text", searchoptions: { "sopt": ["bw", "eq"] } }, { name: "shopTitle", align: 'center', viewable: true, editrules: { edithidden: true }, search: true, editable: true, stype: 'select', edittype: 'select', searchoptions: { sopt: ["eq", "ne"], dataUrl: "/manager/Products/Getshop/", buildSelect: function (data) { var response, s = '<select>', i; response = jQuery.parseJSON(data); if (response && response.length) { $.each(response, function (i) { s += '<option value="' + this.shId + '">' + this.shTitle + '</option>'; }); } return s + '</select>'; }, }, editoptions: { dataUrl: "/manager/Products/Getshop", buildSelect: function (data) { var response, s = '<select>', i; response = jQuery.parseJSON(data); if (response && response.length) { $.each(response, function (i) { s += '<option value="' + this.shId + '">' + this.shTitle + '</option>'; }); } return s + '</select>'; }, } }, { editable: true, name: "groupTitle", search: true, stype: "select" , editrules: { edithidden: true }, search: true, edittype: 'select', editoptions: { dataUrl: "/manager/Products/GetGroups", buildSelect: function (data) { var response, s = '<select>', i; response = jQuery.parseJSON(data); if (response && response.length) { $.each(response, function (i) { s += '<option value="' + this.grpId + '">' + this.grpTitle + '</option>'; }); } return s + '</select>'; }, } }, { editable: true, width: 70, name: "AvailableCount", search: true, stype: "number", searchoptions: { "sopt": ["bw", "eq"] } }, { editable: true, width: 50, name: "Priority", search: true, stype: "number", searchoptions: { "sopt": ["bw", "eq"] } }, { editable: false, width: 80, name: "ComplementProducts", search: true, stype: "text", searchoptions: { "sopt": ["bw", "eq"] } }, { editable: false, width: 70, name: "Images", search: true, stype: "text", searchoptions: { "sopt": ["bw", "eq"] } }, { editable: false, width: 100, name: "ProductProperty", search: true, stype: "text", searchoptions: { "sopt": ["bw", "eq"] } }, { editable: false, width: 80, name: "Price", search: true, stype: "text", searchoptions: { "sopt": ["bw", "eq"] } }, { editable: false, width: 80, name: "Comments", search: true, stype: "text", searchoptions: { "sopt": ["bw", "eq"] } }, { editable: true, width: 50, name: "Active", search: true, formatter: 'checkbox', edittype: 'checkbox', editoptions: { value: "True:False" } , formatoptions: { disabled: false} }, { editable: true, width: 80, name: "AmazingOffer", search: true, formatter: 'checkbox', edittype: 'checkbox', editoptions: { value: "True:False" } , formatoptions: { disabled: false} }, { editable: true, width: 80, name: "Available", search: true, formatter: 'checkbox', edittype: 'checkbox', editoptions: { value: "True:False" }, searchoptions: { "sopt": ["bw", "eq"] }, formatoptions: { disabled: false } }, { name: 'BrandId', align: 'center', hidden: true, viewable: true, editrules: { edithidden: true }, editable: true, stype: 'select', edittype: 'select', editoptions: { dataUrl: "/manager/Products/GetBrands", buildSelect: function (data) { var response, s = '<select>', i; response = jQuery.parseJSON(data); if (response && response.length) { $.each(response, function (i) { s += '<option value="' + this.brandId + '">' + this.brandTitle + '</option>'; }); } return s + '</select>'; }, } }, { name: "myac", width: 80, fixed: true, sortable: false, resize: false, formatter: 'actions', formatoptions: { keys: true } }, { editable: false, width: 70, name: "FullEdit", search: true, stype: "text", searchoptions: { "sopt": ["bw", "eq"] } }, // BLAH, BLAH, BLAH ], gridComplete: function () { var ids = jQuery("#JQGrid1").jqGrid('getDataIDs'); for (var i = 0; i < ids.length; i++) { var cl = ids[i]; ComplementProducts = "<div class=\"btn-group\"><a class='btn btn-primary' href=/manager/ComplementProducts/Index/" + cl + " }) + '><span class=\"fa fa-shopping-cart\" style='color: white;'></span></a></div>"; Images = "<div class=\"btn-group\"><a class='btn btn-primary' href=/manager/Images/Index/" + cl + " }) + '><span class=\"fa fa-picture-o\" style='color: white;'></span></a></div>"; ProductProperty = "<div class=\"btn-group\"><a class='btn btn-primary' href=/manager/ProductPropertyItems/Index/" + cl + " }) + '><span class=\"fa fa-braille\" style='color: white;'></span></a></div>"; Price = "<div class=\"btn-group\"><a class='btn btn-primary' href=/manager/pricesppitems/Index/" + cl + " }) + '><span class=\"fa fa-line-chart\" style='color: white;'></span></a></div>"; Comments = "<div class=\"btn-group\"><a class='btn btn-primary' href=/manager/Products/Comments/" + cl + " }) + '><span class=\"fa fa-comments\" style='color: white;'></span></a></div>"; FullEdit = "<div class=\"btn-group\"><a class='btn btn-primary' href=/manager/Products/Edit/" + cl + " }) + '><span class=\"fa fa-edit\" style='color: white;'></span></a><a class='btn btn-primary' href=/manager/Products/Details/" + cl + " }) + '><span class=\"fa fa-exclamation-circle\" style='color: white;'></span></a></div>"; jQuery("#JQGrid1").jqGrid('setRowData', ids[i], { ComplementProducts: ComplementProducts, Images: Images, ProductProperty: ProductProperty, Price: Price, Comments: Comments, FullEdit: FullEdit}); } }, loadComplete: function () { var activeButton = getColumnIndexByName('Active'); var ids = jQuery("#JQGrid1").jqGrid('getDataIDs'); //id's $("tbody > tr.jqgrow > td:nth-child(" + (activeButton + 1) + ") > input", this).click(function (e) { var rowId = $(e.target).closest("tr").attr("id"); // alert("active clicked " + rowId); $.ajax({ type: "POST", url: "/manager/Products/Activate", data: { id: rowId }, dataType: "json" }); }); var amazingOfferButton = getColumnIndexByName('AmazingOffer'); var ids = jQuery("#JQGrid1").jqGrid('getDataIDs'); //id's $("tbody > tr.jqgrow > td:nth-child(" + (amazingOfferButton + 1) + ") > input", this).click(function (e) { var rowId = $(e.target).closest("tr").attr("id"); $.ajax({ type: "POST", url: "/manager/Products/ShowInAmazingOffer", data: { id: rowId }, dataType: "json" }); $('#JQGrid1').trigger('reloadGrid'); }); var availableButton = getColumnIndexByName('Available'); var ids = jQuery("#JQGrid1").jqGrid('getDataIDs'); //id's $("tbody > tr.jqgrow > td:nth-child(" + (availableButton + 1) + ") > input", this).click(function (e) { var rowId = $(e.target).closest("tr").attr("id"); $.ajax({ type: "POST", url: "/manager/Products/Available", data: { id: rowId }, dataType: "json" }); $('#JQGrid1').trigger('reloadGrid'); }); }, height: "auto", caption: "", viewrecords: true, rowNum: 20, direction: "rtl", pager: jQuery('#JQGrid1_pager'), rowList: [10, 20, 30, 40], toppager: true, jsonReader: { root: "rows", page: "page", total: "total", records: "records", repeatitems: false, Id: "0" }, }).jqGrid('navGrid', '#JQGrid1_pager', // the buttons to appear on the toolbar of the grid { edit: true, add: true, del: true, search: true, refresh: true, view: false, position: "left", cloneToTop: true,searchtext:"جستجو" }, // options for the Edit Dialog { width: 450, editCaption: "ویرایش محصول", recreateForm: true, closeAfterEdit: true, viewPagerButtons: false, //afterShowForm: populateGroups, errorTextFormat: function (data) { return 'Error: ' + data.responseText } }, // Add options { url: '/TabMaster/Create', closeAfterAdd: true }, // Delete options { url: '/manager/Products/Remove' }, { zIndex: 100, caption: "جستجوی محصول", sopt: ['cn'] } );
در مای اس کیو ال برای تعریف یک تابع از ساختار زیر استفاده میکنیم :
DELIMITER $$ CREATE DEFINER=`user_name`@`host_name`|CURRENT_USER PROCEDURE `transition_name`( IN | OUT | INOUT `parameter_name` type(bigint,int , ...) ) SQL SECURITY DEFINER| INVOKER transition_name: BEGIN #----procedure_body END
در قسمت
DEFINER=`user_name`@`host_name`|CURRENT_USER
CURRENT_USER
نکته بعدی : قسمت
SQL SECURITY DEFINER| INVOKER
برای شرح تراکنش، مثال پرداخت بانکی را شرح میدهیم:
DELIMITER $$ CREATE DEFINER=CURRENT_USER PROCEDURE `transition_pay`( #-----------input value IN `pay_value` bigint, IN `admin_id` int, #-------------result code OUT `result` bigint ) SQL SECURITY INVOKER transition_pay: BEGIN DECLARE admin_credit DOUBLE DEFAULT 0; SELECT `Credit` INTO admin_credit FROM `Admin` WHERE `Admin_id` = admin_id #----- transaction body END
اگر بخواهیم به دلیلی قبل از رسیدن به تراکنش آن را کنسل کنیم، میتوان از دستور LEAVE استفاده کرد:
مثال :
IF admin_id=0 THEN set result = -1 ; #exit procedure LEAVE transition_pay; END IF;
START TRANSACTION; INSERT INTO `PayBalance` (`Value` , `Admin_id` ) VALUES (pay_value, admin_id); UPDATE `Admin` SET `Credit`=admin_credit + pay_value WHERE `admin_id`=admin_id; COMMIT;
حال اگر بخواهیم به دلیلی داخل تراکنش آن را لغو کنیم از دستور ROLLBACK استفاده میکنیم.
مثال:
IF pay_value=0 THEN set result = -1 ; #roolback procedure ROLLBACK ; END IF;
SET autocommit = {0 | 1}
نکته آخر اینکه با استفاده ار زبان پی اچ پی هم میتوان تراکنشی را شروع و تمام کرد و بین این دو دستورات مورد نظر را نوشت و همیشه وجود پروسیجر الزامی نیست.
export class Book { constructor( public id, public title:string, public pages:Array ){} } return this._http.get('getBook/1') .map(function(res){ var data = res.json(); return new Book(data.id, data.title, data.pages); })
چرا بعد از عملیات ajax ایی دیگه کار نمیده؟
از کد زیر برای نمایش استفاده شده:
<script type="text/javascript"> $(document).ready(function () { $("[rel='tooltip']").tooltip({ placement: 'top', trigger: 'hover' }); }); </script>
ولی بعد از انجام این دستور دیگه کار نمیده:
@Ajax.ActionLink(" ", MVC.Admin.ContactUs.ActionNames.List, MVC.Admin.ContactUs.Name, new { bywriter = ViewBag.bywriter, bydate = ViewBag.bydate, byisread = ViewBag.byisread, byisshow = ViewBag.byisshow, page = max, count = ViewBag.COUNT }, new AjaxOptions { HttpMethod = "Post", InsertionMode = InsertionMode.Replace, OnBegin = "showLoading", UpdateTargetId = "listdiv", OnComplete = "hideLoading" }, new { @class = "glyphicon glyphicon-backward nodecoration", @rel = "tooltip", @title = "صفحه آخر" })