Response.AddHeader("content-disposition", "inline;filename=sample.pdf");
WhatsUp Gold Syslog Server
- یک ابزار غنی تجمیع پیامهای (لاگهای) شبکه ای با کمک پروتکلهای SNMP و Syslog
- مشاهده پیامهای شبکه در زمان واقعی همراه با فیلتر اطلاعات
- قابل پردازش تا 6 میلیون پیام در ساعت (حدودا 100000 تراکنش در دقیقه)
- ارسال پیامهای شبکه به طور مستقیم و آنلاین به فایل ویندوز
- دریافت پیام از دستگاههای مختلف از طریق پروتکلهای UDP و TCP
- ارسال پیامهای شبکه به صورت آنلاین به سرورهای دیگر با استفاده از پروتکل UDP و TCP
- حفظ آدرس منبع در هنگام ارسال پیام به یک syslog Server دیگر
- اجرا به عنوان یک سرویس ویندوزی (Windows Service) و یا در حالت Windows Application
- فیلتر و مرتب سازی پیامهای syslog برای نمایش داده شدن
- همه این قابلیتها در این نرم افزار تحت دات نت فریم ورک 4 پیاده سازی شده است
Operating system: Windows XP/Vista/7/Server2008
Additional Requirements: Microsoft .NET Framework 4.0
jqGrid
- صفحه بندی و مرتب سازی خودکار اطلاعات به کمک jqGrid در ASP.NET MVC
- فرمت کردن اطلاعات نمایش داده شده به کمک jqGrid در ASP.NET MVC
- فعال سازی و پردازش جستجوی پویای jqGrid در ASP.NET MVC
- فعال سازی و پردازش صفحات پویای افزودن، ویرایش و حذف رکوردهای jqGrid در ASP.NET MVC
- استفاده ازExpressionها جهت ایجاد Strongly typed view در ASP.NET MVC
- سفارشی سازی عناصر صفحات پویای افزودن و ویرایش رکوردهای jqGrid در ASP.NET MVC
- تهیه خروجی PDF و اکسل از حاصل جستجوی پویای jqGrid به کمک PDF Report
- آپلود فایل توسط فرمهای پویای jqGrid
- اعتبارسنجی سفارشی سمت کاربر و سمت سرور در jqGrid
- فعال سازی و پردازش Inline Add در jqGrid
- گروه بندی اطلاعات در jqGrid
- نمایش Subgrid در jqGrid
- ایجاد زیر گریدهای چند سطحی در jqGrid
- نمایش ساختارهای درختی توسط jqGrid
- سبک وزن است
- بسیار سریع است
- به صورت سورس باز توسعه پیدا میکند
- رایگان است
- چندسکویی است
- انواع و اقسام زبانهای برنامه نویسی را پشتیبانی میکند
- پشتیبانی بسیار مناسبی را از طرف جامعهی برنامه نویسان به همراه دارد
- به همراه تعداد زیادی افزونه است
هدف اصلی از توسعهی آن نیز ارائهی تجربهی کاربری یکسانی در سکوهای کاری مختلف و زبانهای متفاوت برنامه نویسی است. در اینجا مهم نیست که از ویندوز، مک یا لینوکس استفاده میکنید. نحوهی کار کردن با آن در این سکوهای کاری تفاوتی نداشته و یکسان است. همچنین برای آن تفاوتی نمیکند که با PHP کار میکنید یا ASP.NET. تمام گروههای مختلف برنامه نویسان دسترسی به یک IDE بسیار سریع و سبک وزن را خواهند داشت.
برخلاف نگارش کامل ویژوال استودیو که این روزها حجم دریافت آن به بالای 20 گیگابایت رسیدهاست، VS Code با هدف سبک وزن بودن و سادگی دریافت و نصب، طراحی و توسعه پیدا میکند. این مورد، مزیت دریافت به روز رسانیهای منظم را بدون نگرانی از دریافت حجمهای بالا، برای بسیاری از علاقمندان مسیر میکند.
همچنین برای کار با نگارشهای جدیدتر ASP.NET Core، دیگر نیازی به دریافت آخرین به روز رسانیهای چندگیگابایتی ویژوال استودیوی کامل نبوده و میتوان کاملا مستقل از آن، از آخرین نگارش NET Core. و ASP.NET Core به سادگی در VSCode استفاده کرد.
نصب VS Code بر روی ویندوز
آخرین نگارش این محصول را از آدرس https://code.visualstudio.com میتوانید دریافت کنید. نصب آن نیز بسیار سادهاست؛ فقط گزینهی Add to PATH را نیز در حین نصب حتما انتخاب نمائید (هرچند به صورت پیش فرض نیز انتخاب شدهاست). به این ترتیب امکان استفادهی از آن در کنسولهای متفاوتی مسیر خواهد شد.
در ادامه فرض کنید که مسیر D:\vs-code-examples\sample01 حاوی اولین برنامهی ما خواهد بود. برای اینکه در اینجا بتوانیم، تجربهی کاربری یکسانی را مشاهده کنیم، از طریق خط فرمان به این پوشه وارد شده و دستور ذیل را صادر میکنیم:
D:\vs-code-examples\sample01>code .
نصب VS Code بر روی Mac
نصب VS Code بر روی مک یا لینوکس نیز به همین ترتیب است و زمانیکه به آدرس فوق مراجعه میکنید، به صورت خودکار نوع سیستم عامل را تشخیص داده و بستهی متناسبی را به شما پیشنهاد میکند. پس از دریافت بستهی آن برای مک، یک application را دریافت خواهید کرد که آنرا میتوان به مجموعهی Applications سیستم اضافه کرد. تنها تفاوت تجربهی نصب آن با ویندوز، انتخاب گزینهی Add to PATH آن است و به صورت پیش فرض نمیتوان آنرا از طریق ترمینال در هر مکانی اجرا کرد. برای این منظور، پس از اجرای اولیهی VS Code، دکمههای Ctrl/Command+Shift+P را در VS Code فشرده و سپس path را جستجو کنید (در دستور یاد شده، Ctrl برای ویندوز و لینوکس است و Command برای Mac):
در اینجا گزینهی install 'code' command path را انتخاب کنید تا بتوان VS Code را از طریق ترمینال نیز به سادگی اجرا کرد. به این ترتیب امکان اجرای دستور . code که بر روی ویندوز نیز ذکر شد، در اینجا نیز میسر خواهد بود.
نصب VS Code بر روی لینوکس
در اینجا نیز با مراجعهی به آدرس https://code.visualstudio.com، بستهی متناسب با لینوکس، جهت دریافت پیشنهاد خواهد شد؛ برای مثال بستههای deb. برای توزیعهایی مانند اوبونتو و یا rpm. برای ردهت. به علاوه اگر بر روی علامت ^ کنار بستههای دانلود کلیک کنید، یک بستهی tar.gz. نیز قابل دریافت خواهد بود. تجربهی نصب آن نیز همانند نمونهی ویندوز است و Add to PATH آن به صورت خودکار انجام خواهد شد.
بررسی ابتدایی محیط VS Code
VS Code بر اساس فایلهای قرار گرفتهی در یک پوشه و زیر پوشههای آن کار میکند. به همین جهت پس از صدور دستور . code، آن پوشه را در IDE خود نمایش خواهد داد. در اینجا برخلاف نگارش کامل ویژوال استودیو، روش کار، مبتنی بر یک فایل پروژه نیست و اگر خارج از VS Code نیز فایلی را به پوشهی باز شده اضافه کنید، بلافاصله تشخیص داده شده و در اینجا لیست میشود. هرچند یک چنین تجربهی کاربری با پروژههای ASP.NET Core نیز در نگارشهای جدیدتر ویژوال استودیوی کامل، سعی شدهاست شبیه سازی شود؛ برخلاف سایر پروژههای ویژوال استودیو که اگر فایلی در فایل پروژهی آن مدخلی نداشته باشد، به صورت پیش فرض نمایش داده نشده و درنظر گرفته نمیشود.
در ادامه برای نمونه از طریق منوی File->New File، یک فایل جدید را اضافه میکنیم. هرچند میتوان اشارهگر ماوس را بر روی نام پوشه نیز برده و از دکمههای نوار ابزار آن نیز برای ایجاد یک فایل و یا پوشهی جدید نیز استفاده کرد:
در اینجا فرمت ابتدایی فایل جدید را plain text تشخیص میدهد:
برای تغییر این حالت یا میتوان فایل را ذخیره کرد و پسوند مناسبی را برای آن انتخاب نمود و یا در همان status bar پایین صفحه، بر روی plain text کلیک کنید تا منوی انتخاب زبان ظاهر شود:
به این ترتیب پیش از ذخیرهی فایل با پسوندی مناسب نیز میتوان زبان مدنظر را تنظیم کرد. پس از آن، intellisense و syntax highlighting متناسب با آن زبان در دسترس خواهند بود.
بررسی تنظیمات VS Code
از طریق منوی File->Preferences->Settings میتوان به تنظیمات VS Code دسترسی یافت.
در اینجا در سمت چپ، لیست تنظیمات مهیا و پیش فرض این محیط قرار دارند و در سمت راست میتوان این پیش فرضها را (پس از بررسی و جستجوی آنها در پنل سمت چپ) بازنویسی و سفارشی سازی کرد.
تنظیمات انجام شدهی در اینجا را میتوان به پوشهی جاری نیز محدود کرد. برای این منظور بر روی لینک work space settings در کنار لینک user settings در تصویر فوق کلیک کنید. در این حالت یک فایل json را در پوشهی vscode. نمای جاری VSCode، ایجاد خواهد کرد (sample01\.vscode\settings.json) که میتواند در برگیرندهی تنظیمات سفارشی محدود و مختص به این پروژه و یا نما باشد.
یک نکته: تمام گزینههای منوی VS Code را و حتی مواردی را که در منوها لیست نشدهاند، میتوانید در Command Pallet آن با فشردن دکمههای Ctrl/Command+Shift+P نیز مشاهده کنید و به علاوه جستجوی آن نیز بسیار سریعتر است از دسترسی و کار مستقیم با منوها.
همچنین در اینجا اگر قصد یافتن سریع فایلی را داشته باشید، میتوانید دکمههای Ctrl/Command+P را فشرده و سپس نام فایل را جستجو کرد:
این دو دستور، جزو دستورات پایهای این IDE هستند و مدام از آنها استفاده میشود.
نصب افزونهی #C
اولین افزونهای را که جهت کار با ASP.NET Core نیاز خواهیم داشت، افزونهی #C است. برای این منظور در نوار ابزار عمودی سمت چپ صفحه، گزینهی Extensions را انتخاب کنید:
در اینجا افزونهی #C مایکروسافت را جستجو کرده و نصب کنید. نصب آن نیز بسیار ساده است. با حرکت اشارهگر ماوس بر روی آن، دکمهی install ظاهر میشود یا حتی اگر آنرا در لیست انتخاب کنیم، در سمت راست صفحه علاوه بر مشاهدهی جزئیات آن، دکمههای نصب و عزل نیز ظاهر خواهند شد.
تجربهی کاربری محیط نصب افزونههای آن نیز نسبت به نگارش کامل ویژوال استودیو، بسیار بهتر است. برای نمونه اگر به تصویر فوق دقت کنید، در همینجا میتوان جزئیات کامل افزونه، نویسنده یا نویسندگان آن و یا لیست تغییرات و وابستگیهای آنرا نیز بدون خروج از VSCode مشاهده و بررسی کرد. همچنین در دفعات بعدی اجرای VSCode، کار بررسی و نصب به روز رسانیهای این افزونهها نیز خودکار بوده و نیازی به بررسی دستی آنها نیست.
پس از نصب، دکمهی reload را ظاهر کرده و با کلیک بر روی آن، محیط جاری به صورت خودکار بارگذاری مجدد شده و بلافاصله قابل استفادهاست.
در قسمت بعد، اولین پروژهی ASP.NET Core خود را در VS Code ایجاد خواهیم کرد.
ng generate component [name] یا ng g c [name]
<template> <v-app> <v-toolbar app> <v-toolbar-title> <span>Vuetify</span> <span>MATERIAL DESIGN</span> </v-toolbar-title> <v-spacer></v-spacer> <v-btn flat href="https://github.com/vuetifyjs/vuetify/releases/latest" target="_blank" > <span>Latest Release</span> </v-btn> </v-toolbar> <v-content> <HelloWorld/> </v-content> </v-app> </template> <script> export default { name: 'App', components: { }, data () { return { // } } } </script>
<template> <v-container v-if="loading"> <div> <v-progress-circular indeterminate :size="150" :width="8" color="green"> </v-progress-circular> </div> </v-container> <v-container v-else grid-list-xl> <v-layout wrap> <v-flex xs4 v-for="(item, index) in wholeResponse" :key="index" mb-2> <v-card> <v-img :src="item.Poster" aspect-ratio="1" ></v-img> <v-card-title primary-title> <div> <h2>{{item.Title}}</h2> <div>Year: {{item.Year}}</div> <div>Type: {{item.Type}}</div> <div>IMDB-id: {{item.imdbID}}</div> </div> </v-card-title> <v-card-actions> <v-btn flat color="green" @click="singleMovie(item.imdbID)" >View</v-btn> </v-card-actions> </v-card> </v-flex> </v-layout> </v-container> </template> <script> import movieApi from '@/services/MovieApi' export default { data () { return { wholeResponse: [], loading: true } }, mounted () { movieApi.fetchMovieCollection('indiana') .then(response => { this.wholeResponse = response.Search this.loading = false }) .catch(error => { console.log(error) }) }, methods: { singleMovie (id) { this.$router.push('/movie/' + id) } } } </script> <style scoped> .v-progress-circular margin: 1rem </style>
<template> <v-container v-if="loading"> <div> <v-progress-circular indeterminate :size="150" :width="8" color="green"> </v-progress-circular> </div> </v-container> <v-container v-else> <v-layout wrap> <v-flex xs12 mr-1 ml-1> <v-card> <v-img :src="singleMovie.Poster" aspect-ratio="2" ></v-img> <v-card-title primary-title> <div> <h2>{{singleMovie.Title}}-{{singleMovie.Year}}</h2> <p>{{ singleMovie.Plot}} </p> <h3>Actors:</h3>{{singleMovie.Actors}} <h4>Awards:</h4> {{singleMovie.Awards}} <p>Genre: {{singleMovie.Genre}}</p> </div> </v-card-title> <v-card-actions> <v-btn flat color="green" @click="back">back</v-btn> </v-card-actions> </v-card> </v-flex> </v-layout> <v-layout row wrap> <v-flex xs12> <div> <v-dialog v-model="dialog" width="500"> <v-btn slot="activator" color="green" dark> View Ratings </v-btn> <v-card> <v-card-title primary-title > Ratings </v-card-title> <v-card-text> <table style="width:100%" border="1" > <tr> <th>Source</th> <th>Ratings</th> </tr> <tr v-for="(rating,index) in this.ratings" :key="index"> <td align="center">{{ratings[index].Source}}</td> <td align="center"><v-rating :half-increments="true" :value="ratings[index].Value"></v-rating></td> </tr> </table> </v-card-text> <v-divider></v-divider> <v-card-actions> <v-spacer></v-spacer> <v-btn color="primary" flat @click="dialog = false" > OK </v-btn> </v-card-actions> </v-card> </v-dialog> </div> </v-flex> </v-layout> </v-container> </template> <script> import movieApi from '@/services/MovieApi' export default { props: ['id'], data () { return { singleMovie: '', dialog: false, loading: true, ratings: '' } }, mounted () { movieApi.fetchSingleMovie(this.id) .then(response => { this.singleMovie = response this.ratings = this.singleMovie.Ratings this.ratings.forEach(function (element) { element.Value = parseFloat(element.Value.split(/\/|%/)[0]) element.Value = element.Value <= 10 ? element.Value / 2 : element.Value / 20 } ) this.loading = false }) .catch(error => { console.log(error) }) }, methods: { back () { this.$router.push('/') } } } </script> <style scoped> .v-progress-circular margin: 1rem </style>
<template> <v-container v-if="loading"> <div> <v-progress-circular indeterminate :size="150" :width="8" color="green"> </v-progress-circular> </div> </v-container> <v-container v-else-if="noData"> <div> <h2>No Movie in API with {{this.name}}</h2> </div> </v-container> <v-container v-else grid-list-xl> <v-layout wrap> <v-flex xs4 v-for="(item, index) in movieResponse" :key="index" mb-2> <v-card> <v-img :src="item.Poster" aspect-ratio="1" ></v-img> <v-card-title primary-title> <div> <h2>{{item.Title}}</h2> <div>Year: {{item.Year}}</div> <div>Type: {{item.Type}}</div> <div>IMDB-id: {{item.imdbID}}</div> </div> </v-card-title> <v-card-actions> <v-btn flat color="green" @click="singleMovie(item.imdbID)" >View</v-btn> </v-card-actions> </v-card> </v-flex> </v-layout> </v-container> </template> <script> // در همه کامپوننتها جهت واکشی اطلاعات ایمپورت میشود import movieApi from '@/services/MovieApi' export default { // route پارامتر مورد استفاده در props: ['name'], data () { return { // آرایه ای برای دریافت فیلمها movieResponse: [], // جهت نمایش لودینگ در زمان بارگذاری اطلاعات loading: true, // مشخص کردن آیا اطللاعاتی با سرچ انجام شده پیدا شده یا خیر noData: false } }, // تعریف متدهایی که در برنامه استفاده میکنیم methods: { // این تابع باعث میشود که // route // تعریف شده با نام // Movie // فراخوانی شود و آدرس بار هم تغییر میکنید به آدرسی شبیه زیر // my-site/movie/tt4715356 singleMovie (id) this.$router.push('/movie/' + id) }, fetchResult (value) { movieApi.fetchMovieCollection(value) .then(response => { if (response.Response === 'True') { this.movieResponse = response.Search this.loading = false this.noData = false } else { this.noData = true this.loading = false } }) .catch(error => { console.log(error) }) } }, // جز توابع // life cycle // vue.js // میباشد و زمانی که تمپلیت رندر شد اجرا میشود // همچنین با هر بار تغییر در // virtual dom // این تابع اجرا میشود mounted () { this.fetchResult(this.name) }, // watchها // کار ردیابی تغییرات را انجام میدهند و به محض تغییر مقدار پراپرتی // name // کد مورد نظر در بلاک زیر انجام میشود watch: { name (value) { this.fetchResult(value) } } } </script> <style scoped> .v-progress-circular margin: 1rem </style>
import axios from 'axios'
import axios from 'axios' export default { fetchMovieCollection (name) { return axios.get('&s=' + name) .then(response => { return response.data }) }, fetchSingleMovie (id) { return axios.get('&i=' + id) .then(response => { return response.data }) } }
axios.defaults.baseURL = 'http://www.omdbapi.com/?apikey=b76b385c&page=1&type=movie&Content-Type=application/json'
import Vue from 'vue' import VueRouter from 'vue-router' // برای رجیستر کردن کامپوننتها در بخش روتر، آنها را ایمپورت میکنیم import LatestMovie from '@/components/LatestMovie' import Movie from '@/components/Movie' import SearchMovie from '@/components/SearchMovie' Vue.use(VueRouter) export default new VueRouter({ routes: [ { // مسیری هست که برای این کامپوننت در نظر گرفته شده(صفحه اصلی)بدون پارامتر path: '/', // نام روت name: 'LatestMovie', // نام کامپوننت مورد نظر component: LatestMovie }, { // پارامتری هست که به این کامپوننت ارسال میشه id // برای دستیابی به این کامپوننت نیاز هست با آدرسی شبیه زیر اقدام کرد // my-site/movie/tt4715356 path: '/movie/:id', name: 'Movie', // در کامپوننت جاری یک پراپرتی وجود دارد //id که میتوان با نام // به آن دسترسی پیدا کرد props: true, component: Movie }, { path: '/search/:name', name: 'SearchMovie', props: true, component: SearchMovie } ], // achieve URL navigation without a page reload // When using history mode, the URL will look "normal," e.g. http://oursite.com/user/id. Beautiful! // در آدرس # قرار نمیگیرد mode: 'history' })
npm install
<input type="file" name="Image1" id="Image1" multiple />
[HttpPost] public ActionResult UploadFiles(HttpPostedFileBase[] image1, int id) { var isAjax = this.Request.IsAjaxRequest(); Thread.Sleep(3000); return Json(new { FileName = "/Uploads/filename.ext" }, "text/html", JsonRequestBehavior.AllowGet); }
تبدیل یک View به رشته و بازگشت آن به همراه نتایج JSON حاصل از یک عملیات Ajax ایی در ASP.NET MVC
ممکن است بخواهیم در پاسخ یک تقاضای Ajax ایی، اگر عملیات در سمت سرور با موفقیت انجام شد، خروجی یک Controller action را به کاربر نهایی نشان دهیم. در
چنین سناریویی لازم است که بتوانیم خروجی یک action را
بصورت رشته برگردانیم. در این مقاله به این مسئله خواهیم پرداخت .
فرض کنید در یک سیستم وبلاگ ساده قصد داریم امکان کامنت گذاشتن بصورت Ajax را پیاده سازی کنیم. یک ایده عملی و کارآ
این است: بعد از اینکه کاربر متن کامنت را وارد کرد و دکمهی ارسال کامنت را زد،
تقاضا به سمت سرور ارسال شود و اگر سرور پیغام موفقیت را صادر کرد، متن نوشته شده
توسط کاربر را به کمک کدهای JavaScript و در همان سمت کلاینت بصورت یک
کادر کامنت جدید به محتوای صفحه اضافه کنیم. بنده در اینجا برای اینکه بتوانم اصل
موضوع مورد بحث را توضیح دهم، از یک سناریوی جایگزین استفاده میکنم؛ کاربر موقعیکه دکمه ارسال را زد، تقاضا به سرور ارسال میشود. سرور بعد از انجام عملیات، تحت یک
شی JSON هم نتیجهی انجام عملیات و هم
محتوای HTML نمایش کامنت جدید در صفحه را به سمت کلاینت
ارسال خواهد کرد و کلاینت در صورت موفقیت آمیز بودن عملیات، آن محتوا را به صفحه
اضافه میکند.
با توجه به توضیحات داده شده، ابتدا یک شیء نیاز داریم تا بتوانیم توسط آن نتیجهی عملیات Ajax ایی را بصورت JSON به سمت کلاینت ارسال کنیم:
public class MyJsonResult { public bool success { set; get; } public bool HasWarning { set; get; } public string WarningMessage { set; get; } public int errorcode { set; get; }
public string message {set; get; } public object data { set; get; } }
سپس به متدی نیاز داریم که کار تبدیل نتیجهی action را به رشته، انجام دهد:
public static string RenderViewToString(ControllerContext context, string viewPath, object model = null, bool partial = false) { ViewEngineResult viewEngineResult = null; if (partial) viewEngineResult = ViewEngines.Engines.FindPartialView(context, viewPath); else viewEngineResult = ViewEngines.Engines.FindView(context, viewPath, null); if (viewEngineResult == null) throw new FileNotFoundException("View cannot be found."); var view = viewEngineResult.View; context.Controller.ViewData.Model = model; string result = null; using(var sw = new StringWriter()) { var ctx = new ViewContext(context, view, context.Controller.ViewData, context.Controller.TempData, sw); view.Render(ctx, sw); result = sw.ToString(); } return result; }
فرض کنیم در سمت Controller هم از کدی شبیه به این استفاده میکنیم:
public JsonResult AddComment(CommentViewModel model) { MyJsonResult result = new MyJsonResult() { success = false; }; if (!ModelState.IsValid) { result.success = false; result.message = "لطفاً اطلاعات فرم را کامل وارد کنید"; return Json(result); } try { Comment theComment = model.toCommentModel(); //EF service factory Factory.CommentService.Create(theComment); Factory.SaveChanges(); result.data = Tools.RenderViewToString(this.ControllerContext, "/views/posts/_AComment", model, true); result.success = true; } catch (Exception ex) { result.success = false; result.message = "اشکال زمان اجرا"; } return Json(result); }
و در سمت کلاینت برای ارسال Form به صورت Ajax ایی خواهیم داشت:
@using (Ajax.BeginForm("AddComment", "posts", new AjaxOptions() { HttpMethod = "Post", OnSuccess = "AddCommentSuccess", LoadingElementId = "AddCommentLoading" }, new { id = "frmAddComment", @class = "form-horizontal" })) { @Html.HiddenFor(m => m.PostId) <label for="fname">@Texts.ContactName</label> <input type="text" id="fname" name="FullName" class="form-control" placeholder="@Texts.ContactName "> <label for="email">@Texts.Email</label> <input type="email" id="InputEmail" name="email" class="form-control" placeholder="@Texts.Email"> <br><textarea name="C_Content" cols="60" rows="10" class="form-control"></textarea><br> <input type="submit" value="@Texts.SubmitComments" name="" class="btn btn-primary"> <div class="loading-mask" style="display:none">@Texts.LoadingMessage</div> }
باید توجه شود Texts در اینجا یک Resource هست که به منظور نگهداری کلمات استفاده شده در سایت، برای زبانهای مختلف استفاده میشود (رجوع شود به مفهوم بومی سازی در Asp.net) .
و در قسمت script ها داریم:
<script type="text/javascript"> function AddCommentSuccess(jsData) { if (jsData && jsData.message) alert(jsData.message); if (jsData && jsData.success) { document.getElementById("frmAddComment").reset(); //افزودن کامنت جدید ساخته شده توسط کاربر به لیست کامنتهای صفحه $("#divAllComments").html(jsData.data + $("#divAllComments").html()); } } </script>
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت سوم - تکمیل مستندات یک API با کامنتها
[HttpPost("two-files")] public async Task Upload(IFormFile file1, IFormFile file2) { // validate the files, scan virus, save them to a file storage }
و یا حالت پیچیدهتر آن
/// <summary> /// Submit a form which contains a key-value pair and a file. /// </summary> /// <param name="id">Student ID</param> /// <param name="form">A form which contains the FormId and a file</param> /// <returns></returns> [HttpPost("{id:int}/forms")] [ProducesResponseType(typeof(FormSubmissionResult), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task<ActionResult<FormSubmissionResult>> SubmitForm(int id, [FromForm] StudentForm form) { // ... } public class StudentForm { [Required] public int FormId { get; set; } [Required] public IFormFile StudentFile { get; set; } }
اگر قصد اجرای برخی کارها به صورت زمانبندی شده و در فواصل زمانی مشخص را دارید، این مقاله به شما کمک خواهد کرد تا به بهترین شکل ممکن آن را انجام دهید. کارهایی مانند ارسال خبرنامه، فرستادن SMS تبریک تولد یا هماهنگ سازی دادهها بین دو منبع داده از جمله اَعمالی هستند که باید به صورت زمانبندی شده انجام شوند.
کتابخانهی Quartz.NET، از کتابخانه ای با نام Quartz و از زبان Java به NET. منتقل شده است. Quartz.NET، رایگان و باز متن است و از طریق آدرس http://quartznet.sourceforge.net در دسترس است. از طریق NuGet نیز میتوانید با تایپ عبارت quartz در فرم مربوطه، این کتابخانه را نصب کنید. این کتابخانه را در برنامههای Desktop و Web (حتی یک Shared Server) تست کردم و به خوبی انجام وظیفه میکند.
شروع کار با Quartz.NET
ضمن در اختیار قرار دادن امکانات فوق العاده و انعطاف پذیری بسیار، کار با این کتابخانه آسان و از فرایندی منطقی تبعیت میکند. فرایند اجرای یک روال زمانبندی شده از طریق Quartz.NET، از چهار مرحلهی اصلی تشکیل شده است.
1) پیاده سازی اینترفیس IJob
2) مشخص کردن جزئیات روال با اینترفیس IJobDetail
3) مشخص کردن تنظیمات زمان با استفاده از اینترفیس ITrigger
4) مدیریت اجرا با استفاده از اینترفیس IScheduler
مثالی را بررسی میکنیم. در این مثال قصد داریم تا عبارتی را همراه با تاریخ و زمان جاری در یک فایل ذخیره کنیم. این پیغام باید 3 بار و در فواصل زمانی 10 ثانیه به فایل اضافه شود. در پایان، فایلی خواهیم داشت که در سه خط، یک عبارت، همراه با تاریخ و زمانهای مختلف را که 10 ثانیه با یکدیگر اختلاف دارند در خود ذخیره کرده است. ابتدا کار زمانبندی شده را با ارائهی پیاده سازی برای متد Execute اینترفیس IJob این کتابخانه ایجاد میکنیم. وارد کردن فضای نام Quartz را فراموش نکنید.
namespace SchedulerDemo.Jobs { using System; using System.IO; using Quartz; public class HelloJob : IJob { public void Execute(IJobExecutionContext context) { // for web apps // string path = System.Web.Hosting.HostingEnvironment.MapPath("~/Data/Log.txt"); // for desktop apps string path = @"C:\Log.txt"; using (StreamWriter sw = new StreamWriter(path, true)) { sw.WriteLine("Message from HelloJob " + DateTime.Now.ToString()); } } } }
حال، زمان انجام تنظیمات مختلف برای اجرای روال مربوطه است. بهتر است تا interfaceیی ایجاد و متدی با نام Run در آن داشته باشیم.
namespace SchedulerDemo.Interfaces { public interface ISchedule { void Run(); } }
حال، پیاده سازی خود را برای این interface ارائه میدهیم.
namespace SchedulerDemo.Jobs { using System; using Quartz; using Quartz.Impl; using SchedulerDemo.Interfaces; using SchedulerDemo.Jobs; public class HelloSchedule : ISchedule { public void Run() { //DateTimeOffset startTime = DateBuilder.NextGivenSecondDate(null, 2); DateTimeOffset startTime = DateBuilder.FutureDate(2, IntervalUnit.Second); IJobDetail job = JobBuilder.Create<HelloJob>() .WithIdentity("job1") .Build(); ITrigger trigger = TriggerBuilder.Create() .WithIdentity("trigger1") .StartAt(startTime) .WithSimpleSchedule(x => x.WithIntervalInSeconds(10).WithRepeatCount(2)) .Build(); ISchedulerFactory sf = new StdSchedulerFactory(); IScheduler sc = sf.GetScheduler(); sc.ScheduleJob(job, trigger); sc.Start(); } } }
معرفی فضاهای نام Quartz و Quartz.Impl را فراموش نکنید.
از حالا، به روالی که قرار است به صورت زمانبندی شده اجرا شود، "وظیفه" میگوییم.
ابتدا باید مشخص کنیم که وظیفه در چه زمانی پس از اجرای برنامه شروع به اجرا کند. از آنجا که پایه و اساس زمانبندی، بر تاریخ و ساعت استوار است، کتابخانهی Quartz.NET، روشها و امکانات بسیاری را برای تعیین زمان در اختیار قرار میدهد. با بررسی تمامی آنها، سادهترین و منعطفترین را به شما معرفی میکنم. کلاس DateBuilder که همراه با Quartz.NET وجود دارد، امکان تعیین زمان را به اَشکال مختلف میدهد. در خط 14، از متد FutureDate این کلاس استفاده شده است که خوانایی بهتری نسبت به بقیهی متدها دارد. پارامتر اول این متد، عدد، و پارامتر دوم، واحد زمانی را میپذیرد.
DateTimeOffset startTime = DateBuilder.FutureDate(2, IntervalUnit.Second);
در اینجا، زمان آغاز وظیفه را 2 ثانیه پس از آغاز برنامه تعریف کرده ایم. واحدهای زمانی دیگر شامل میلی ثانیه، دقیقه، ساعت، روز، ماه، هفته و سال هستند. کلاس DateBuilder، متدهای مختلفی برای تعیین زمان را در اختیار قرار میدهد. تعیین زمان آغاز به روش دیگر را به صورت کامنت شده در خط 13 مشاهده میکنید.
وظیفهی ایجاد شده در خط 16 تا 18 معرفی شده است.
IJobDetail job = JobBuilder.Create<HelloJob>() .WithIdentity("job1") .Build();
پشتیبانی Quartz.NET از سینتکس fluent، کدنویسی را ساده و لذت بخش میکند. با استفاده از متد Create کلاس JobBuilder، وظیفه را معرفی میکنیم. متد Create، یک متد Generic است که نام کلاسی که اینترفیس IJob را پیاده سازی کرده است میپذیرد. یک نام را با استفاده از متد WithIdentity به وظیفه نسبت میدهیم (البته این کار، اختیاری است) و در انتها، متد Build را فراخوانی میکنیم. خروجی متد Build، از نوع IJobDetail است.
و حالا نوبت به تنظیمات زمان رسیده است. در Quartz.NET، این مرحله، "ایجاد trigger" نام دارد. خطوط 20 تا 24 به این کار اختصاص دارند.
ITrigger trigger = TriggerBuilder.Create() .WithIdentity("trigger1") .StartAt(startTime) .WithSimpleSchedule(x => x.WithIntervalInSeconds(10).WithRepeatCount(2)) .Build();
ابتدا متد Create کلاس TriggerBuilder را فراخوانی میکنیم، سپس با استفاده از متد WithIdentity، یک نام به trigger اختصاص میدهیم (البته این کار، اختیاری است). با متد StartAt، زمان شروع وظیفه را که در ابتدا با استفاده از کلاس DateBuilder ایجاد کردیم تعیین میکنیم. مهمترین قسمت، تعیین دفعات و فواصل زمانی اجرای وظیفه است. همان طور که احتمالاً حدس زده اید، Quartz.NET مجموعه ای غنی از روشهای مختلف برای تعیین بازهی زمانی اجرا را در اختیار قرار میدهد. آسانترین راه، استفاده از متد WithSimpleSchedule است. با استفاده از یک عبارت Lambda که ورودی آن از نوع کلاس SimpleScheduleBuilder است، دفعات و فواصل زمانی اجرا را تعیین میکنیم. متد WithIntervalInSeconds، برای تعیین فواصل زمانی در بازهی ثانیه استفاده میشود. متد WithRepeatCount نیز برای تعیین دفعات اجرا است. وظیفهی ما، 3 مرتبه و در فواصل زمانی 10 ثانیه اجرا میشود. مطمئن باشید اشتباه نکردم! بله، سه مرتبه. تعداد دفعات اجرا برابر است با عددی که برای متد WithRepeatCount تعیین میکنید، به علاوهی یک. منطقی است، چون مرتبهی اول اجرا زمانی است که با استفاده از متد StartAt تعیین کرده اید. در پایان، متد Build را فراخوانی میکنیم. خروجی متد Build، از نوع ITrigger است.
آخرین کار (خطوط 26 تا 30)، ایجاد شی از اینترفیس IScheduler، فراخوانی متد ScheduleJob آن، و پاس دادن اشیای job و trigger که در قسمت قبل ایجاد شده اند به این متد است. در انتها، متد ()Start را برای آغاز وظیفه فراخوانی میکنیم.
ISchedulerFactory sf = new StdSchedulerFactory(); IScheduler sc = sf.GetScheduler(); sc.ScheduleJob(job, trigger); sc.Start();
حال شما یک وظیفه تعریف کرده اید که در هر جای برنامه به صورت زیر، قابل فراخوانی است.
ISchedule myTask = new HelloSchedule(); myTask.Run();
کتابخانه ای که با آن سر و کار داریم بسیار غنی است و امکانات بسیاری دارد. در قسمت بعد، با برخی امکانات دیگر این کتابخانه آشنا میشوید.
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 تان لذت ببرید.