تایپوگرافی
هدف از تایپوگرافی، چیدمان متن به نحوی است که واضح، خوانا و مشخص باشد؛ همچنین مباحث زیبایی ارائه را نیز به آن اضافه کنید. برای مثال تنظیم فاصله بین حروف و کلمات، فاصله بین خطوط و یا رعایت یک سری نسبتهای ویژه مانند نسبت طلایی جهت دعوت خواننده به مطالعه مطالب، بجای فراری دادن او، در مباحث تایپوگرافی رعایت میشوند. خوشبختانه Twitter Bootstrap به همراه یک سری تنظیمات تایپوگرافی پیش فرض است که در ادامه آنها را مرور خواهیم کرد.
پیش فرضهای ابتدایی آنرا مانند قلم با اندازه 14px، قلم پیش فرض Helvetica، فاصله بین خطوط و رنگ متن را در فایل bootstrap.css میتوانید مشاهده کنید:
body { margin: 0;"Helvetica Neue", Helvetica, Arial, sans-serif; color: #333333; background-color: #ffffff; }
<div class="row-fluid"> <div class="span12"> <h1> سرتیتر 1 </h1> <h2> سرتیتر 2 </h2> <h3> سرتیتر 3 </h3> <h4> سرتیتر 4 </h4> <h5> سرتیتر 5 </h5> <h6> سرتیتر 6 </h6> </div> </div> |
<div class="row-fluid"> <div class="span12"> <p class="lead"> تیتر</p> <p> متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن متن </p> <small>اندازه کوچک</small> <strong>متن ضخیم</strong> <em>متن ایتالیک</em> </div> </div>
روش دیگر جلب توجه به یک متن در bootstrap، استفاده از کلاسهای text مانند text-error و امثال آن میباشد:
<div class="row-fluid"> <div class="span12"> <p class="muted"> متن غیرفعال</p> <p class="text-warning"> نمایش اخطار به کاربر</p> <p class="text-error"> نمایش خطا به کاربر</p> <p class="text-info"> نمایش اطلاعات به کاربر</p> <p class="text-success"> نمایش موفقیت آمیز بودن عملیات</p> </div> </div> |
<div class="row-fluid"> <div class="span12"> <p> My <abbr title="منظور واحد پردازش مرکزی است"> CPU</abbr> has N Cores. </p> </div> </div> |
<div class="row-fluid"> <div class="span12"> <address> وحید نصیری <br /> ایران، تهران <br /> <abbr title="Phone"> P:</abbr> 12345678 <br /> <abbr title="Cell"> C:</abbr> 12345678 </address> </div> </div> |
<div class="row-fluid"> <div class="span12"> <blockquote> <p> جهت نمایش نقل قول </p> <small><cite title="کاربر شماره 2">از شخصی</cite> </small> </blockquote> </div> </div> |
<div class="row-fluid"> <div class="span6"> <ul class="unstyled"> <li>یک </li> <li>دو </li> <li>سه </li> </ul> </div> <div class="span6"> <ol> <li>یک </li> <li>دو </li> <li>سه </li> </ol> </div> </div> |
<div class="row-fluid"> <div class="span12"> <dl class="dl-horizontal"> <dt>عنوان</dt> <dd> توضیحات بلند در اینجا</dd> </dl> </div> </div> |
<div class="row-fluid"> <div class="span6"> <code>inline code</code> </div> <div class="span6"> <pre class="pre-scrollable"> code code code </pre> </div> </div> |
استفاده از جداول و تاثیر bootstrap بر آنها
در ادامه کدهای یک جدول متداول را که مزین شدهاست به کلاسهای bootstrap ملاحظه میکنید:
<div class="container-fluid"> <div class="row-fluid"> <div class="span12"> <table class="table table-striped table-hover table-bordered table-condensed"> <caption> عنوانی خاص در اینجا</caption> <thead> <tr> <th> ستون یک </th> <th> ستون دو </th> <th> ستون سه </th> </tr> </thead> <tbody> <tr class="error"> <td> 1 متن یک </td> <td> 1 متن دو </td> <td> 1 متن سه </td> </tr> <tr> <td> 2 متن یک </td> <td> 2 متن دو </td> <td> 2 متن سه </td> </tr> <tr> <td> 3 متن یک </td> <td> 3 متن دو </td> <td> 3 متن سه </td> </tr> </tbody> </table> </div> </div> </div>
در اینجا ذکر caption اختیاری است. وجود thead و tbody به تشخیص هدر و ردیفها جهت اعمال شیوهنامههای متناظر کمک میکنند. همچنین کلاسهای ذیل نیز به جدول اعمال شدهاند:
table: سبب میشود تا تنظیمات ابتدایی bootstrap به جدول طراحی شده، اعمال شوند.
table-striped: رنگ زمینه سطرها را یک در میان تغییر میدهد.
table-hover: سبب میشود تا با عبور اشارهگر ماوس از روی سطرها، رنگ زمینه آنها تغییر کنند.
table-bordered: حاشیهای را به جدول و ردیفها اعمال میکنند. همچنین سبب نمایش گوشههای گرد نیز میشود.
table-condensed: اندکی padding اعمال شده به سلولهای جداول را کاهش میدهد و جدول را فشردهتر میکند.
در جدول فوق، کلاس نمونه error به یک tr نیز اعمال شدهاست تا اثر آنرا بر روی یک ردیف بهتر بتوان ملاحظه کرد.
طراحی فرمها و تاثیر bootstrap بر آنها
قرار دادن برچسبها و عناصر صفحه، به نحوی کاربرپسند و دلپذیر، نیاز به رعایت یک سری اصول تایپوگرافی و طراحی ویژه دارد که این موارد نیز در bootstrap گنجانده شدهاند.
1) فرمهای عمودی
<div class="container-fluid"> <div class="row-fluid"> <div class="span12"> <form action="/signup" method="post"> <fieldset> <legend>ثبت نام</legend> <label> ایمیل:</label> <input name="email" type="text" /> <label> نام:</label> <input name="name" type="text" /> </fieldset> </form> </div> </div> </div> |
همانطور که ملاحظه میکنید، کلیه عناصر را به صورت یک پشته عمودی در صفحه قرار دادهاست و نکته مهم اینجا است که هیچگونه شیوه نامه خاص و اضافهتری به فرم فوق از طرف ما اعمال نشده است.
نکته: اگر میخواهید همان حالت پیش فرض مرورگر، یعنی قرار دادن تمام عناصر در پشت سر هم را فعال کنید، تنها کافی است کلاس form-inline را به form تعریف شده فوق اضافه نمائید.
2) نکاتی در مورد checkboxes و radio buttons
در حالت پیش فرض، با تعریف یک label و checkbox، برچسب متناظر با آن اندکی بالاتر از checkbox قرار گرفته شده در صفحه ظاهر میشود. برای رفع این مشکل تنها کافی است کلاس checkbox به label اعمال شود (و برای radio button از کلاس radio استفاده خواهد شد):
<label class="checkbox"> <input type="checkbox" name="isMale" /> مذکر</label>
نکته: برای قرار دادن چندین checkbox یا radio button در یک سطر (با توجه به حالت چیدمان عمودی پیش فرض فرمها)، ابتدا آنها را داخل یک div قرار دهید. سپس به تمام checkboxها یا radio buttonها کلاس inline را نیز اضافه نمائید. برای مثال:
<div> <label class="radio inline"> <input type="radio" name="isMale" /> مذکر</label> <label class="radio inline"> <input type="radio" name="isFemale" /> مؤنث</label> </div> |
4) تعیین اندازه فیلدها
اگر علاقمند هستید که یک textarea کل عرض صفحه را به خود اختصاص دهد، از کلاس input-block-level استفاده کنید.
5) فرمهای جستجو
<form class="search-form"> <fieldset> <legend>جستجو</legend> <input type="search" class="search-query" /> <button type="submit" class="btn" >بیاب</button> </fieldset> </form> |
- کلاس فرم بهتر است search-form تعیین شود
- نوع input بهتر است search وارد شود
- کلاس input جستجو نیز search-query انتخاب گردد
همانطور که ملاحظه میکنید، در این حالت گوشههای جعبه متنی جستجو، نسبت به حالتهای معمولی آنها گرد شده است. کلاس دکمه نیز btn درنظر گرفته شده است تا حالت ویژه دکمههای bootstrap را پیدا کند.
6) فرمهای افقی
تا اینجا، با فرمهای حالت پیش فرض یا فرمهایی که عناصر را به صورت پشتهای عمودی بر روی یکدیگر قرار میدهند، آشنا شدیم. حالت متداول دیگر طراحی فرمها، حالت افقی است. به این معنا که در هر سطر، یک برچسب و یک المان قرار گیرند، بجای اینکه ابتدا برچسب نمایش داده شود و در سطر بعدی، المان مرتبط با آن. Bootstrap برای طراحی بدون استفاده از جداول این نوع فرمها نیز تنظیمات خاصی را تدارک دیدهاست.
<form class="form-horizontal" action="/signup" method="post"> <fieldset> <legend>ثبت نام</legend> <div class="control-group"> <label class="control-label"> ایمیل:</label> <div class="controls"> <input name="email" type="text" /> </div> </div> <div class="control-group"> <label class="control-label"> نام:</label> <div class="controls"> <input name="name" type="text" /> </div> </div> <div class="control-group"> <div class="controls"> <label class="radio inline"> <input type="radio" name="isMale" /> مذکر</label> <label class="radio inline"> <input type="radio" name="isFemale" /> مؤنث</label> </div> </div> </fieldset> </form> |
- کلاس form-horizontal را به فرم جاری اضافه کنید.
- هر سطر مورد نظر را در div ایی با کلاس control-group محصور نمائید.
- به برچسبها، کلاس control-label را انتساب دهید.
- کنترلهای مدنظر را در div ایی با کلاس controls محصور کنید.
هر چند این روش نیاز به اندکی HTML نویسی دارد، اما بسیاری به این نوع فرمها بیشتر علاقمند هستند تا فرمهای عمودی ابتدای بحث.
7) جلب توجه کاربران به فیلدها برای نمایش خطاهای اعتبارسنجی
<div class="control-group error"> <label class="control-label"> ایمیل:</label> <div class="controls"> <input name="email" type="text" /> <span class="help-block">لطفا ایمیل را با فرمت صحیحی وارد نمائید</span> </div> </div> |
8) توسعه فیلدهای استاندارد
<div class="input-prepend input-append"> <span dir="ltr" class="add-on">.00</span> <input dir="ltr" type="text" /> <span class="add-on">ریال</span> </div> |
9) HTML Helpers مخصوص ASP.NET MVC برای کار با bootstrap
نکاتی را که در اینجا مطرح شدند، اگر علاقمند بودید که به شکلی strongly typed در ASP.NET MVC اعمال کنید، میتوان به پروژههایی مانند TwitterBootstrapMvc مراجعه کرد. تعداد این نوع پروژهها هم روز به روز بیشتر میشوند:
https://twitterbootstrapmvc.codeplex.com/
https://mvc4bootstaphelper.codeplex.com/
https://github.com/erichexter/twitter.bootstrap.mvc
http://bootstraphelpers.codeplex.com/
تنظیمات خاص دکمهها در حین استفاده از Twitter Bootstrap
در مثال ذیل، کلاسهای مرتبط با تزئین دکمهها را توسط bootstrap، ملاحظه میکنید:
<div class="container-fluid"> <div class="row-fluid"> <div class="span12"> <table class="table table-striped table-hover table-bordered table-condensed"> <thead> <tr> <th> دکمه </th> <th> لینک </th> <th> کلاس بکار گرفته شده </th> </tr> </thead> <tbody> <tr> <td> <button class="btn"> default</button> </td> <td> <a href="#" class="btn">default</a> </td> <td> <code>btn</code> </td> </tr> <tr> <td> <button class="btn btn-primary"> primary</button> </td> <td> <a href="#" class="btn btn-primary">primary</a> </td> <td> <code>btn btn-primary</code> </td> </tr> <tr> <td> <button class="btn btn-info"> info</button> </td> <td> <a href="#" class="btn btn-info">info</a> </td> <td> <code>btn btn-info</code> </td> </tr> <tr> <td> <button class="btn btn-success"> success</button> </td> <td> <a href="#" class="btn btn-success">success</a> </td> <td> <code>btn btn-success</code> </td> </tr> <tr> <td> <button class="btn btn-warning"> warning</button> </td> <td> <a href="#" class="btn btn-warning">warning</a> </td> <td> <code>btn btn-warning</code> </td> </tr> <tr> <td> <button class="btn btn-danger"> danger</button> </td> <td> <a href="#" class="btn btn-danger">danger</a> </td> <td> <code>btn btn-danger</code> </td> </tr> <tr> <td> <button class="btn btn-inverse"> inverse</button> </td> <td> <a href="#" class="btn btn-inverse">inverse</a> </td> <td> <code>btn btn-inverse</code> </td> </tr> <tr> <td> <button class="btn btn-link"> link</button> </td> <td> <a href="#" class="btn btn-link">link</a> </td> <td> <code>btn btn-link</code> </td> </tr> <tr> <td> <button class="btn btn-primary btn-large"> large</button> </td> <td> <a href="#" class="btn btn-primary btn-large">large</a> </td> <td> <code>btn btn-primary btn-large</code> </td> </tr> <tr> <td> <button class="btn btn-primary btn-small"> small</button> </td> <td> <a href="#" class="btn btn-primary btn-small">small</a> </td> <td> <code>btn btn-primary btn-small</code> </td> </tr> <tr> <td> <button class="btn btn-primary btn-mini"> mini</button> </td> <td> <a href="#" class="btn btn-primary btn-mini">mini</a> </td> <td> <code>btn btn-primary btn-mini</code> </td> </tr> <tr> <td> <button class="btn btn-primary btn-block"> block</button> </td> <td> <a href="#" class="btn btn-primary btn-block">block</a> </td> <td> <code>btn btn-primary btn-block</code> </td> </tr> <tr> <td> <button class="btn btn-primary disabled"> disabled</button> </td> <td> <a href="#" class="btn btn-primary disabled">disabled</a> </td> <td> <code>btn btn-primary disabled</code> </td> </tr> </tbody> </table> </div> </div> </div>
همانطور که ملاحظه کردید، الزامی ندارد که این کلاسها را حتما به دکمهها اعمال کرد. برای نمونه میتوان از یک span یا لینک نیز برای تعریف دکمهها بهره جست. برای یکپارچه سازی چنین دکمههایی (که در اصل دکمه نیستند) با ASP.NET MVC میتوان به مطلب تکمیلی «استفاده از دکمههای CSS توئیتر در ASP.NET MVC» مراجعه نمود.
یک نکته: اگر علاقمند هستید که تعدادی دکمه را به شکل یک toolbar نمایش دهید، آنها را در یک div محصور کرده و کلاس btn-group را به آن div اعمال نمائید.
کار با تصاویر و آیکونها در Twitter Bootstrap
کلاسهایی مانند img-rounded، img-circle، img-polaroid با اعمال به یک تصویر، سبب گردن شدن گوشههای آن، نمایش دایرهای و یا نمایش به همراه حاشیه یک تصویر خواهند شد.
Twitter Bootstrap به همراه صدها آیکون ارائه شده است. این آیکونها توسط glyphicons.com ایجاد شدهاند. روشی که برای استفاده از آنها توصیه شده است، استفاده از تگ i میباشد. برای مثال:
<i class="icon-music"></i>icon-music
برای مشاهده لیست کلاسهای قابل استفاده، کلمه icon-glass را در فایل bootstrap.css جستجو نمائید؛ تا شروع مدخل مرتبط با آیکونها را بتوانید مشاهده نمائید.
رنگ پیش فرض این آیکونها مشکی است. اگر علاقمند بودید که آنها را برای مثال با رنگ سفید نمایش دهید فقط کافی است کلاس icon-white را پس از کلاس آیکون مدنظر،ذکر کرد:
<i class="icon-music icon-white"></i>icon-music
امکان اعمال این آیکونها به دکمهها نیز وجود دارد. برای مثال:
<button class="btn"> <i class="icon-music"></i>دکمه</button>
<div class="input-prepend"> <span class="add-on"><i class="icon-envelope"></i></span> <input type="email" dir="ltr" /> </div> |
در مقالهی قبل توانستیم یک سری
از مدلهای مربوط به وبلاگ را آماده کنیم. در ادامه به تکمیل آن و همچین
آغاز تهیهی مدلهای مربوط به اخبار و پیغام خصوصی میپردازیم.
همکاران این قسمت:
سلمان معروفی
مدل گزارش دهی
/// <summary> /// Repersents a Report template for every cms section /// </summary> public class Report { #region Ctor /// <summary> /// Create one instance for <see cref="Report"/> /// </summary> public Report() { ReportedOn = DateTime.Now; Id = SequentialGuidGenerator.NewSequentialGuid(); } #endregion #region Properties /// <summary> /// gets or sets identifier for Report /// </summary> public virtual Guid Id { get; set; } /// <summary> /// gets or sets reason of report /// </summary> public virtual string Reason { get; set; } /// <summary> /// gets or sets section that is reported /// </summary> public virtual ReportSection Section { get; set; } /// <summary> /// gets or sets sectionid that is reported /// </summary> public virtual long SectionId { get; set; } /// <summary> /// gets or sets type of report /// </summary> public virtual ReportType Type{ get; set; } /// <summary> /// gets or sets report's datetime /// </summary> public virtual DateTime ReportedOn { get; set; } /// <summary> /// indicate this report is read by admin /// </summary> public virtual bool IsRead { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets id of user that is reporter /// </summary> public virtual long ReporterId { get; set; } /// <summary> /// gets or sets id of user that is reporter /// </summary> public virtual User Reporter { get; set; } #endregion } /// <summary> /// Represents Report Section /// </summary> public enum ReportSection { News, Poll, Announcement, ForumTopic, BlogComment, BlogPost, NewsComment, PollComment, AnnouncementComment, ForumPost, User, ... } /// <summary> /// Represents Type of Report /// </summary> public enum ReportType { Spam, Abuse, Advertising, ... }
قصد داریم در این سیستم به کاربران خاصی دسترسی گزارش دادن در بخشهای مختلف را بدهیم. این دسترسیها در بخش تنظیمات سیستم قابل تغییر خواهند بود (برای مثال براساس امتیاز ، براساس تعداد پست و ... ) . این امکان میتواند برای مدیریت سیستم مفید باشد.
برای سیستم گزارش دهی به مانند سیستم امتیاز دهی عمل خواهیم کرد. در کلاس Report، خصوصیت ReportSection از نوع دادهی شمارشی میباشد که در بالا تعریف آن نیز آماده است و مشخص کنندهی بخشهایی میباشد که لازم است امکان گزارش دهی داشته باشند. خصوصیت Type هم که از نوع شمارشی ReportType میباشد، مشخص کنندهی نوع گزارشی است که داده شده است.
علاوه بر نوع گزارش، میتوان دلیل گزارش را هم ذخیره کرد که برای این منظور خصوصیت Reason در نظر گرفته شدهاست. خصوصیت IsRead هم برای مدیریت این گزارشات در پنل مدیریت در نظر گرفته شده است. اگر در مقالهی قبل دقت کرده باشید، متوجه وجود خصوصیتی به نام ReportsCount در کلاس BaseContent و BaseComment خواهید شد که برای نشان دادن تعداد گزارشهایی است که برای آن مطلب یا نظر داده شده است، استفاده میشود.
کلاس پایه فایلهای ضمیمه
/// <summary> /// Represents a base class for every attachment /// </summary> public abstract class BaseAttachment { #region Ctor public BaseAttachment() { Id = SequentialGuidGenerator.NewSequentialGuid(); AttachedOn = DateTime.Now; } #endregion #region Properties /// <summary> /// sets or gets identifier for attachment /// </summary> public virtual Guid Id { get; set; } /// <summary> /// sets or gets name for attachment /// </summary> public virtual string FileName { get; set; } /// <summary> /// sets or gets type of attachment /// </summary> public virtual string ContentType { get; set; } /// <summary> /// sets or gets size of attachment /// </summary> public virtual long Size { get; set; } /// <summary> /// sets or gets Extention of attachment /// </summary> public virtual string Extension { get; set; } /// <summary> /// sets or gets bytes of data /// </summary> //public byte[] Data { get; set; } /// <summary> /// sets or gets Creation Date /// </summary> public virtual DateTime AttachedOn { get; set; } /// <summary> /// gets or sets counts of download this file /// </summary> public virtual long DownloadsCount { get; set; } /// <summary> /// gets or sets datetime that is modified /// </summary> public virtual DateTime ModifiedOn { get; set; } /// <summary> /// gets or sets section that this file attached there /// </summary> public virtual AttachmentSection Section { get; set; } /// <summary> /// gets or sets information of user agent /// </summary> public virtual string Agent { get; set; } #endregion #region NavigationProperties /// <summary> /// sets or gets identifier of attachment's owner /// </summary> public virtual long OwnerId { get; set; } /// <summary> /// sets or gets identifier of attachment's owner /// </summary> public virtual User Owner { get; set; } #endregion } public enum AttachmentSection { News, Announcement, ForumTopic, Conversation, BlogComment, NewsComment, PollComment, AnnouncementComment, ForumPost, BlogPost, Group, ... }
کلاس بالا اکثر خصوصیات لازم برای مدل Attachment ما را در خود دارد. قصد داریم از ارث بری TPH برای مدیریت فایلهای ضمیمه استفاده کنیم. در سیستم بستهی ما، تنها کاربران احراز هویت شده میتوانند فایل ضمیمه کنند و برای همین منظور OwnerId را که همان ارسال کنندهی فایل میباشد، به صورت Nullable در نظر نگرفتهایم.
یک سری از مشخصات که نیاز به توضیح اضافی ندارند، ولی خصوصیت AttachmentSection که از نوع شمارشی AttachmentSection است، برای دسترسی راحت کاربر به فایلهای ارسالی خود در پنل کاربری در نظر گرفته شده است. برای بخشهای (وبلاگ - اخبار - نظرسنجیها - آگهیها - انجمن) که نیاز به Privacy خاصی نیست و احراز هویت کفایت میکند، مدل زیر را در نظر گرفته ایم:
مدل فایلهای ضمیمه عمومی
/// <summary> /// Repersent the attachment for file /// </summary> public class Attachment : BaseAttachment { }
/// <summary> /// Represents one news item /// </summary> public class NewsItem : BaseContent { #region Ctor /// <summary> /// create one instance of <see cref="NewsItem"/> /// </summary> public NewsItem() { Rating = new Rating(); PublishedOn = DateTime.Now; } #endregion #region Properties /// <summary> /// indicating that this news show on sidebar /// </summary> public virtual bool ShowOnSideBar { get; set; } /// <summary> /// indicate this NewsItem is approved by admin if NewsItem.Moderate==true /// </summary> public virtual bool IsApproved { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets newsitem's Reviews /// </summary> public ICollection<NewsComment> Comments { get; set; } #endregion }
کلاس بالا نشان دهندهی اشتراکهای ما خواهند بود. این مدل ما هم از کلاس پایهی BaseContent بحث شده در مقالهی قبل، ارث بری کرده و علاوه بر آن دو خصوصیت دیگر تحت عنوان IsApproved برای اعمال مدیریتی در نظر گرفته شده است (اگر در بخش تنظیمات سیستم اخبار، مدیریت تصمیم گرفته باشد تا اخبار جدید به اشتراک گذاشته شده با تأیید مدیریتی منتشر شوند) و خصوصیت ShowOnSideBar هم به عنوان یک تنظیم مدیریتی برای خبر خاصی در نظر گرفته شده که لازم است به صورت sticky در سایدبار نمایش داده شود.
برای اخبار نیز امکان ارسال نظر خواهیم داشت که برای این منظور لیستی از مدل زیر (NewsComment) در مدل بالا تعریف شده است .
مدل نظرات اخبار
public class NewsComment : BaseComment { #region Ctor public NewsComment() { Rating = new Rating(); CreatedOn = DateTime.Now; } #endregion #region NavigationProperties /// <summary> /// gets or sets body of blog NewsItem's comment /// </summary> public virtual long? ReplyId { get; set; } /// <summary> /// gets or sets body of blog NewsItem's comment /// </summary> public virtual NewsComment Reply { get; set; } /// <summary> /// gets or sets body of blog NewsItem's comment /// </summary> public virtual ICollection<NewsComment> Children { get; set; } /// <summary> /// gets or sets NewsItem that this comment sent to it /// </summary> public virtual NewsItem NewsItem { get; set; } /// <summary> /// gets or sets NewsItem'Id that this comment sent to it /// </summary> public virtual long NewsItemId { get; set; } #endregion }
/// <summary> /// Indicate one conversation /// </summary> public class Conversation { #region Ctor /// <summary> /// create one instance of <see cref="Conversation"/> /// </summary> public Conversation() { Id = SequentialGuidGenerator.NewSequentialGuid(); SentOn = DateTime.Now; } #endregion #region Properties /// <summary> /// gets or sets identifier of record /// </summary> public virtual Guid Id { get; set; } /// <summary> /// represents this conversaion is seen /// </summary> public virtual bool IsRead { get; set; } /// <summary> /// gets or sets subject of this conversation /// </summary> public virtual string Subject { get; set; } /// <summary> /// gets or sets Date that this record added /// </summary> public virtual DateTime SentOn { get; set; } /// <summary> /// indicate this record deleted by sender /// </summary> public virtual bool DeletedBySender { get; set; } /// <summary> /// indicate this record deleted by receiver /// </summary> public virtual bool DeletedByReceiver { get; set; } /// <summary> /// gets or sets Messagescount that Unread by sender of this conversation /// </summary> public virtual int UnReadSenderMessagesCount { get; set; } /// <summary> /// gets or sets Messagescount that Unread by receiver of this conversation /// </summary> public virtual int UnReadReceiverMessagesCount { get; set; } /// <summary> /// gets or sets Messagescount of this conversation for increase performance /// </summary> public virtual int MessagesCount { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets if of user that start this conversation /// </summary> public virtual long SenderId { get; set; } /// <summary> /// gets or sets user that start this conversation /// </summary> public virtual User Sender { get; set; } /// <summary> /// gets or sets id of user that is recipient /// </summary> public virtual long ReceiverId { get; set; } /// <summary> /// gets or sets user that is recipient /// </summary> public virtual User Receiver { get; set; } /// <summary> /// get or set Messages of this conversation /// </summary> public virtual ICollection<ConversationReply> Messages { get; set; } /// <summary> /// get or set Attachments that attached in this conversation /// </summary> public virtual ICollection<ConversationAttachment> Attachments { get; set; } #endregion
مدل بالا نشان دهندهی گفتگوی بین دو کاربر میباشد. هر گفتگو امکان دارد با موضوع خاصی ایجاد شود و مسلما یک کاربر بهعنوان دریافت کننده و کاربر دیگری بعنوان ارسال کننده خواهد بود. برای این منظور خصوصیات Receiver و Sender که از نوع User هستند را در این کلاس در نظر گرفتهایم.
خصوصیات DeletedBySender و DeletedByReceiver هم برای این در نظر گفته شدهاند که اگر یک طرف این گفتگو خواهان حذف آن باشد، برای آن کاربر حذف نرم انجام دهیم و فعلا برای کاربر مقابل قابل دسترسی باشد.
UnReadSenderMessagesCount و UnReadReceiverMessagesCount هم برای بالا بردن کارآیی سیستم در نظر گفته شدهاند و در واقع تعداد پیغامهای خوانده نشده در یک گفتگو به صورت متمایز برای هر دو طرف، ذخیره میشود. هر گفتگو شامل یکسری پیغام رد و بدل شده خواهد بود که بدین منظور لیستی از ConversationReplyها را در مدل بالا تعریف کردهایم.
در هر گفتگو یکسری فایل هم ممکن است ضمیمه شود ، برای این منظور هم یک لیستی از کلاس ConversationAttachment در مدل گفتگو تعریف شده است که در ادامه پیاده سازی کلاس ConversationAttachment را هم خواهیم دید.
مدل ConversationReply به شکل زیر میباشد:
/// <summary> /// Represents One Reply to Conversation /// </summary> public class ConversationReply { #region Ctor /// <summary> /// create one instance of <see cref="ConversationReply"/> /// </summary> public ConversationReply() { Id = SequentialGuidGenerator.NewSequentialGuid(); SentOn = DateTime.Now; } #endregion #region Properties /// <summary> /// gets or sets identifier of record /// </summary> public virtual Guid Id { get; set; } /// <summary> /// represents this conversaionReply is seen /// </summary> public virtual bool IsRead { get; set; } /// <summary> /// gets or sets body of this conversationReply /// </summary> public virtual string Body { get; set; } /// <summary> /// gets or sets Date that this record added /// </summary> public virtual DateTime SentOn { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets Parent's Id Of this ConversationReply /// </summary> public virtual Guid? ParentId { get; set; } /// <summary> /// gets or sets Parent Of this ConversationReply /// </summary> public virtual ConversationReply Parent { get; set; } /// <summary> /// get or set Children Of this ConversationReply /// </summary> public virtual ICollection<ConversationReply> Children { get; set; } /// <summary> /// gets or sets if of user that start this conversationReply /// </summary> public virtual long SenderId { get; set; } /// <summary> /// gets or sets user that start this conversationReply /// </summary> public virtual User Sender { get; set; } /// <summary> /// gets or sets Conversation that this message sent in it /// </summary> public virtual Conversation Conversation{ get; set; } /// <summary> /// gets or sets Id of Conversation that this message sent in it /// </summary> public virtual Guid ConversationId { get; set; } #endregion }
مدل بالا نشان دهندهی پیغامهای داده شده در یک گفتگو با موضوعی خاص میباشد. ساختار درختی آن هم برای ایجاد امکان جواب دهی برای پیغامها در نظر گرفته شده است (الزامی نیست). هر پیغام در یک گفتگو ارسال شده و یک ارسال کننده نیز دارد که برای این منظور به ترتیب دو خصوصیت Conversation از نوع کلاس Conversation و Sender از نوع User در نظر گرفتهایم.
با توجه به وجود Privacy در گفتگو نیاز است تا مدل فایل ضمیمه بخش گفتگوها به شکل زیر باشد:
/// <summary> /// Represents the attachment That attached in Conversation /// </summary> public class ConversationAttachment : BaseAttachment { #region NavigationProperties public virtual Conversation Conversation { get; set; } public virtual Guid? ConversationId { get; set; } #endregion }
همانطور که کمی بالاتر بحث شد، قصد اعمال ارث بری TPH را برای مدیریت فایلهای ضمیمه داریم. برای این منظور مدل بالا نیز از کلاس BaseAttachment ارث بری کرده و دو خصوصیت اضافه هم برای اعمال ارتباط یک به چند با گفتگو خواهد داشت. توجه کنید که ConversationId به صورت Nullable تعریف شدهاست.
نتیجه این قسمت
تصویر فوق، یکی از تصویرهایی است که شاید از طریق ایمیلهایی تحت عنوان "فقط در ایران!" به دست شما هم رسیده باشد. تصور کاربر نهایی (که این ایمیل را با تعجب ارسال کرده) این است که در اینجا به او گفته شده مثلا "مرتضی" را جستجو نکنید و امثال آن. چون برای او تفاوتی بین ی و ی وجود ندارد. همچنین بکار بردن "اقلامی" هم کمی غلط انداز است و بیشتر ذهن را به سمت کلمه سوق میدهد تا حرف.
در ادامهی بحث آلرژی مزمن به وجود انواع "ی" و "ک" در بانک اطلاعاتی (+ و + و +)، اینبار قصد داریم این اطلاعات را به NHibernate بسط دهیم. شاید یک روش اعمال یک دست سازی "ی" و "ک" این باشد که در کل برنامه هر جایی که قرار است update یا insert ایی صورت گیرد، خواص رشتهای را یافته و تغییر دهیم. این روش "کار میکنه" ولی ایده آل نیست؛ چون حجم کار تکراری در برنامه زیاد خواهد شد و نگهداری آن هم مشکل میشود. همچنین امکان فراموش کردن اعمال آن هم وجود دارد.
در NHibernate یک سری EventListener وجود دارند که کارشان گوش فرا دادن به یک سری رخدادها مانند مثلا update یا insert است. این رخدادها میتوانند پیش یا پس از هرگونه ثبت یا ویرایشی در برنامه صادر شوند. بنابراین بهترین جایی که جهت اعمال این نوع ممیزی (Auditing) بدون بالا بردن حجم برنامه یا اضافه کردن بیش از حد یک سری کد تکراری در حین کار با NHibernate میتوان یافت، روالهای مدیریت کنندهی همین EventListener ها هستند.
کلاس YeKeAuditorEventListener نهایی با پیاده سازی IPreInsertEventListener و IPreUpdateEventListenerبه شکل زیر خواهد بود:
using NHibernate.Event;
namespace NHYeKeAuditor
{
public class YeKeAuditorEventListener : IPreInsertEventListener, IPreUpdateEventListener
{
// Represents a pre-insert event, which occurs just prior to performing the
// insert of an entity into the database.
public bool OnPreInsert(PreInsertEvent preInsertEvent)
{
var entity = preInsertEvent.Entity;
CorrectYeKe.ApplyCorrectYeKe(entity);
return false;
}
// Represents a pre-update event, which occurs just prior to performing the
// update of an entity in the database.
public bool OnPreUpdate(PreUpdateEvent preUpdateEvent)
{
var entity = preUpdateEvent.Entity;
CorrectYeKe.ApplyCorrectYeKe(entity);
return false;
}
}
}
تا اینجا فقط تعریف YeKeAuditorEventListener انجام شده است. اما NHibernate چگونه از وجود آن مطلع خواهد شد؟
برای تزریق کلاس YeKeAuditorEventListener به تنظیمات برنامه باید به شکل زیر عمل کرد:
using System;
using System.Linq;
using FluentNHibernate.Cfg;
using NHibernate.Cfg;
namespace NHYeKeAuditor
{
public static class MappingsConfiguration
{
public static FluentConfiguration InjectYeKeAuditorEventListener(this FluentConfiguration fc)
{
return fc.ExposeConfiguration(configListeners());
}
private static Action<Configuration> configListeners()
{
return
c =>
{
var listener = new YeKeAuditorEventListener();
c.EventListeners.PreInsertEventListeners =
c.EventListeners.PreInsertEventListeners
.Concat(new[] { listener })
.ToArray();
c.EventListeners.PreUpdateEventListeners =
c.EventListeners.PreUpdateEventListeners
.Concat(new[] { listener })
.ToArray();
};
}
}
}
کدهای NHYeKeAuditor را از اینجا میتوانید دریافت کنید.
سپس روی Web Site ایجادشده راست کلیک کنید و از منوی بازشده Property Pages را انتخاب کنید. روی گزینهی Add Reference کلیک کنید، سپس پروژهی MyNewsWCFLibrary را از قسمت Solution انتخاب کرده و دکمهی OK را بفشارید.
دکمهی OK را بفشارید و از Solution Explorer فایل Web.Config را باز کنید. پیش از تغییرات مد نظر باید چنین محتوایی داشته باشد:
<?xml version="1.0" encoding="utf-8"?> <!-- For more information on how to configure your ASP.NET application, please visit http://go.microsoft.com/fwlink/?LinkId=169433 --> <configuration> <system.web> <compilation debug="true" targetFramework="4.5" /> <httpRuntime targetFramework="4.5" /> </system.web> </configuration>
متن آنرا به این صورت تغییر دهید:
<?xml version="1.0" encoding="utf-8"?> <!-- For more information on how to configure your ASP.NET application, please visit http://go.microsoft.com/fwlink/?LinkId=169433 --> <configuration> <system.web> <compilation debug="true" targetFramework="4.5" /> <httpRuntime targetFramework="4.5" /> </system.web> <system.serviceModel> <serviceHostingEnvironment> <serviceActivations> <add factory="System.ServiceModel.Activation.ServiceHostFactory" relativeAddress="./HamedService.svc" service="MyNewsWCFLibrary.MyNewsService"/> </serviceActivations> </serviceHostingEnvironment> <behaviors> <serviceBehaviors> <behavior> <serviceMetadata httpGetEnabled="true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
همانگونه که مشاهده میکنید به وسیلهی تگ add factory سرویسها را به وبسایت معرفی میکنیم. با relativeAddress میتوانیم هر نامی را به عنوان نام سرویس که در URL قرار میگیرد معرفی کنیم. چنانکه من یه جای MyNewsService از نام HamedService استفاده کردم. و در صفت service فضای نام و نام کلاس سرویس را معرفی میکنیم.
اکنون پروژه را اجرا کنید. در مرورگر باید صفحه را به اینصورت مشاهده کنید:
نیازی به یادآوری نیست که شما میتوانید این پروژه را در IIS سرور راهاندازی کنید تا کلیهی مشتریها به آن دسترسی داشته باشند. هرچند پیش از آن باید امنیت را نیز در WCF برقرار کنید.
توجه داشته باشید که روشی که در این بخش به عنوان میزبانی WCF مطرح کردم یکی از روشهای میزبانی WCF است. مثلاً شما میتوانستید به جای ایجاد یک WCFLibrary و یک Web Site به صورت جداگانه یک پروژه از نوع WCF Service و یا Web Site ایجاد میکردید و سرویسها و مدل Entity Framework را به طور مستقیم در آن میافزودید. روشی که در این درس از آن بهره برده ایم البته مزایایی دارد از جمله اینکه خروجی پروژه فقط یک فایل DLL است و با هر بار تغییر فقط کافی است همان فایل را در پوشه Bin از وبسایتی که روی سرور میگذارید کپی کنید.
در بخش هشتم با هم یک پروژهی تحت ویندوز خواهیم ساخت و از سرویس WCF ای که ساخته ایم در آن استفاده خواهیم کرد.
آشنایی با مفهوم Indexer در C#.NET
در حالت عادی میشود بر روی یک وهله از کلاس، Index اعمال کرد. اگر نخواهیم روی کل وهله اعمال شود چطور؟ برای مثال فقط روی یک خاصیت خاص.
پیاده سازی آن یک نکته کوچک دارد که به شرح زیر است:
using System; namespace IndexedProperties { public class Data { private int[] _localArray; private ArrayIndexer _arrayIndexer; public Data() { _localArray = new int[10]; for (int i = 0; i < 10; i++) _localArray[i] = i + 1; _arrayIndexer = new ArrayIndexer(this); } public ArrayIndexer Number { get { return _arrayIndexer; } } public class ArrayIndexer { private Data _arrayOwner; public ArrayIndexer(Data arrayOwner) { _arrayOwner = arrayOwner; } public int this[int index] { get { return _arrayOwner._localArray[index]; } } public int Length { get { return _arrayOwner._localArray.Length; } } } } class Program { static void Main(string[] args) { var data = new Data(); for (int i = 0; i < 10; i++) Console.WriteLine(data.Number[i]); } } }
به این ترتیب تعاریف آرایه مورد استفاده نیازی نیست مستقیما در معرض دید قرار گیرد و همچنین read-only هم تعریف شده است. به علاوه اینکه indexerها محدود به int نیستند و برای مثال میتوان از string و غیره نیز استفاده کرد.
اعمال کنترل دسترسی پویا در پروژههای ASP.NET Core با استفاده از AuthorizationPolicyProvider سفارشی
در مطلب «سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاستهای دسترسی پویا» به طور مفصل به قضیه کنترل دسترسی پویا در ASP.NET Core Identity پرداخته شدهاست؛ در این مطلب روش دیگری را بررسی خواهیم کرد.
مشخص میباشد که بدون وابستگی به روش خاصی، خیلی ساده میتوان به شکل زیر عمل کرد:
services.AddAuthorization(options => { options.AddPolicy("View Projects", policy => policy.RequireClaim(CustomClaimTypes.Permission, "projects.view")); });
[Authorize("View Projects")] public IActionResult Index(int siteId) { return View(); }
Using a large range of policies (for different room numbers or ages, for example), so it doesn’t make sense to add each individual authorization policy with an AuthorizationOptions.AddPolicy call.
کار با پیاده سازی واسط IAuthorizationPolicyProvider شروع میشود؛ یا شاید ارث بری از DefaultAuthorizationPolicyProvider رجیستر شدهی در سیستم DI و توسعه آن هم کافی باشد.
public class AuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider { public AuthorizationPolicyProvider(IOptions<AuthorizationOptions> options) : base(options) { } public override Task<AuthorizationPolicy> GetPolicyAsync(string policyName) { if (!policyName.StartsWith(PermissionAuthorizeAttribute.PolicyPrefix, StringComparison.OrdinalIgnoreCase)) { return base.GetPolicyAsync(policyName); } var permissionNames = policyName.Substring(PermissionAuthorizeAttribute.PolicyPrefix.Length).Split(','); var policy = new AuthorizationPolicyBuilder() .RequireClaim(CustomClaimTypes.Permission, permissionNames) .Build(); return Task.FromResult(policy); } }
متد GetPolicyAsync موظف به یافتن و بازگشت یک Policy ثبت شده میباشد؛ با این حال میتوان با بازنویسی آن و با استفاده از وهلهای از AuthorizationPolicyBuilder، فرآیند تعریف سیاست درخواست شده را که احتمالا در تنظیمات آغازین پروژه تعریف نشده و پیشوند مدنظر را نیز دارد، خوکار کرد. در اینجا امکان ترکیب کردن چندین دسترسی را هم خواهیم داشت که برای این منظور میتوان دسترسیهای مختلف را به صورت comma separated به سیستم معرفی کرد.
نکتهی مهم در تکه کد بالا مربوط است به PolicyPrefix که با استفاده از آن مشخص کردهایم که برای هر سیاست درخواستی، این فرآیند را طی نکند و موجب اختلال در سیستم نشود.
پس از پیاده سازی واسط مطرح شده، لازم است این پیاده سازی جدید را به سیستم DI هم معرفی کنید:
services.AddSingleton<IAuthorizationPolicyProvider, AuthorizationPolicyProvider>();
خوب، تا اینجا فرآیند تعریف سیاستها به صورت خودکار انجام شد. در ادامه نیاز است با تعریف یک فیلتر Authorization، بتوان لیست دسترسیهای مورد نظر برای اکشنی خاص را نیز مشخص کرد تا در متد GetPolicyAsync فوق، کار ثبت خودکار سیاست دسترسی متناظر با آنرا توسط فراخوانی متد policyBuilder.RequireClaim، انجام دهد تا دیگر نیازی به تعریف دستی و جداگانهی آن، در کلاس آغازین برنامه نباشد. برای این منظور به شکل زیر عمل خواهیم کرد:
public class PermissionAuthorizeAttribute : AuthorizeAttribute { internal const string PolicyPrefix = "PERMISSION:"; /// <summary> /// Creates a new instance of <see cref="AuthorizeAttribute"/> class. /// </summary> /// <param name="permissions">A list of permissions to authorize</param> public PermissionAuthorizeAttribute(params string[] permissions) { Policy = $"{PolicyPrefix}{string.Join(",", permissions)}"; } }
همانطور که مشخص میباشد، رشته PERMISSION به عنوان پیشوند رشته تولیدی از لیست اسامی دسترسیها، استفاده شدهاست و در پراپرتی Policy قرار داده شدهاست. این بار برای کنترل دسترسی میتوان به شکل زیر عمل کرد:
[PermissionAuthorize(PermissionNames.Projects_View)] public IActionResult Get(FilteredQueryModel query) { //... } [PermissionAuthorize(PermissionNames.Projects_Create)] public IActionResult Post(ProjectModel model) { //... }
برای مثال در اولین فراخوانی فیلتر PermissionAuthorize فوق، مقدار ثابت PermissionNames.Projects_View به عنوان یک Policy جدید به متد GetPolicyAsync کلاس AuthorizationPolicyProvider سفارشی ما ارسال میشود. چون دارای پیشوند «:PERMISSION» است، مورد پردازش قرار گرفته و توسط متد policyBuilder.RequireClaim به صورت خودکار به سیستم معرفی و ثبت خواهد شد.
همچنین راه حل مطرح شده برای مدیریت دسترسیهای پویا، در gist به اشتراک گذاشته شده «موجودیتهای مرتبط با مدیریت دسترسیهای پویا» را نیز مد نظر قرار دهید.
ASP.NET MVC #18
public class EfRole : EfGenericService<Role>, IRole { public EfRole(IUnitOfWork uow) : base(uow) { } public bool IsUserInRole(string username, string roleName) { using (var context = new PublishingContext()) { var user = context.Users.Where(x => x.Username.Equals(username, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault(); var roles = from ur in user.Rolls from r in context.Rolls where ur.Id == r.Id select r.Rol; if (user != null) return roles.Any(x => x.Equals(roleName, StringComparison.CurrentCultureIgnoreCase)); else return false; } } }
روی متدهای GetAllCategory و GetAllNews به صورت جداگانه کلیک کنید. متوجه خواهید شد که هرچند در کلاس tblNews شیای از نوع tblCategory و در کلاس tblCategory شیای از نوع مجموعهی tblNews به صورت Virtual تعریف شده است ولی در بر خلاف انتظارمان اثری از آن در اینجا دیده نمیشود. نتیجهی مشاهدهشده به خاطر است که در هر دو تعریف صفت DataMember را به ویژگیهای ناوبری اختصاص نداده ایم و این میتواند راهبرد ما در طراحی WCF باشد. ولی اگر میخواهید ویژگی ناوبری میان موجودیتها در متدهای ما هم دیده شود ادامهی این درس را بخوانید وگرنه ممکن است تصمیم داشته باشید در صورت نیاز به پیوند میان موجودیتها، متد جدیدی بنویسید و از دستورهای Linq استفاده کنید و یا برای اینکار از Stored Procedured بهره ببرید.
در اینجا من این سناریو را دنبال میکنم که در صورتی که متد GetAllNews اجرا شود؛ بدون اینکه نیاز باشد برای دانستن نام دستهی خبر از متد دیگری مانند GetAllCategory استفاده کنیم؛ رکورد وابسته موجودیت دسته در هر خبر نشان داده شود.
از Solution Explorer فایل MyNewsModel.tt را باز کنید و دنبال کد زیر بگردید:
public string NavigationProperty(NavigationProperty navigationProperty) { var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType()); return string.Format( CultureInfo.InvariantCulture, "{0} {1} {2} {{ {3}get; {4}set; }}", AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)), navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType, _code.Escape(navigationProperty), _code.SpaceAfter(Accessibility.ForGetter(navigationProperty)), _code.SpaceAfter(Accessibility.ForSetter(navigationProperty))); }
سپس آنرا به صورت زیر ویرایش کنید:
public string NavigationProperty(NavigationProperty navigationProperty) { var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType()); return string.Format( CultureInfo.InvariantCulture, "{0}{1} {2} {3} {{ {4}get; {5}set; }}", navigationProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many ? "[DataMember]" + Environment.NewLine : "", AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)), navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType, _code.Escape(navigationProperty), _code.SpaceAfter(Accessibility.ForGetter(navigationProperty)), _code.SpaceAfter(Accessibility.ForSetter(navigationProperty))); }
پس از ذخیرهی فایل، خواهید دید که صفت DataMember در کلاس tblNews پیش از ویژگی tblCategory افزوده شده است. بار دیگر پروژه را اجرا کنید. روی متد GetAllNews کلیک کنید و روی دکمه Invoke بفشارید. خواهید دید که هرچند tblCategory در ویژگیهای آن قرار گرفته است ولی مقدار آن Null است. برای حل این مشکل باید از Solution Explorer فایل MyNewsService.cs را باز کنید و به به جای کد مربوط به متدهای GetAllNews و GetNews کدهای زیر را قرار دهید:
public List<tblNews> GetAllNews()
{
return dbMyNews.tblNews.Include(p=>p.tblCategory).Where(c=>c.IsDeleted == false).ToList();
}
public tblNews GetNews(int tblNewsId)
{
return dbMyNews.tblNews.Include(p => p.tblCategory).FirstOrDefault(p => p.tblNewsId == tblNewsId);
}
این بار اگر پروژه را اجرا کنید با نتیجهای مانند شکل زیر روبهرو خواهید شد:
در بخش هفتم پیرامون میزبانی WCF Library خواهم نوشت.
من یه وظیفه نوشتم که مییاد هر 1 دقیقه 1 بار یه متن رو در Table درج میکنه.
این کد کلاس
using Quartz; using System.Data; using System.Data.OleDb; using System.Configuration; namespace WebApplication1 { public class TestQuartzClass:IJob { public void Execute(IJobExecutionContext context) { //Page myPage = (Page)HttpContext.Current.Handler; //TextBox MyTextBox=(TextBox)myPage.FindControl("txt"); string sql = "Insert into tbl_Test (Content) values (@Content)"; ExecuteNoneQuery(System.Data.CommandType.Text, sql, new OleDbParameter[]{ //new OleDbParameter("@Content", MyTextBox.Text) new OleDbParameter("@Content","Hello world!") }); } public int ExecuteNoneQuery(CommandType commandType, string commandText, params OleDbParameter[] commandParameters) { using (OleDbConnection con = new OleDbConnection(ConfigurationManager.ConnectionStrings["ConStr"].ConnectionString)) { OleDbCommand cmd = new OleDbCommand(); cmd.Connection = con; cmd.CommandType = commandType; cmd.CommandText = commandText; cmd.Parameters.AddRange(commandParameters); con.Open(); int retVal = cmd.ExecuteNonQuery(); con.Close(); return retVal; } } } }
و این هم کد صفحه ای که با کلیک دکمه وظیفه شروع به کار میکنه
using System; using Quartz; using Quartz.Impl; namespace WebApplication1 { public partial class WebForm1 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } public static void ConfigureQuartzJobs() { // construct a scheduler factory ISchedulerFactory schedFact = new StdSchedulerFactory(); // get a scheduler IScheduler sched = schedFact.GetScheduler(); sched.Start(); IJobDetail job = JobBuilder.Create<TestQuartzClass>() .WithIdentity("SendJob") .Build(); var trigger = TriggerBuilder.Create() .WithIdentity("SendTrigger") .WithSimpleSchedule(x => x.WithIntervalInMinutes(1).RepeatForever()) //.StartAt(startTime) .StartNow() .Build(); sched.ScheduleJob(job, trigger); } protected void btn_Click(object sender, EventArgs e) { ConfigureQuartzJobs(); } } }
همونطور که میبینید متن رو به شکل زیر پاس دادم.
new OleDbParameter("@Content","Hello world!")
و برای اینکه در کلاس بتونم به کنترلهای صفحه دسترسی داشته باشم کدهای زیر رو به متد Exceute اضافه کردم
Page myPage = (Page)HttpContext.Current.Handler; TextBox MyTextBox=(TextBox)myPage.FindControl("txt");
ولی به محض اینکه این کدها رو اضافه میکنم دیگه برنامه کار نمیکنه.
متن داخل تکست باکس رو هم قصد داشتم به شکل زیر پاس بدم.
new OleDbParameter("@Content", MyTextBox.Text)
لطفاً راهنمایی کنید.
من یک نمونه هم به منظور تست آماده کردم که از لینک زیر میتونید دانلود کنید:
http://www.4shared.com/rar/1Fu_jpOOba/WebApplication1.html