Public Class User { public string Id { get; set; } public string PhoneNumber { get; set; } public Dictionary<string, App> Apps { get; set; } } public class App { public string FirstName { get; set; } public string LastName { get; set; } public string UserName { get; set; } public List<string> Roles { get; set; } public List<String> Messages { get; set; } public String AdressId { get; set; } public bool IsActive { get; set; } = true; [JsonIgnore] public string DisplayName => $"{FirstName} {LastName}"; }
services.AddSingleton<IDocumentStore>(serviceProvider => { var store = new DocumentStore() { Urls = new[] { "http://192.168.1.10:8080" }, Database = "AccountingSystem", }.Initialize(); return store; }); services.AddScoped<IAsyncDocumentSession>(serviceProvider => { var store = serviceProvider.GetRequiredService<IDocumentStore>(); return store.OpenAsyncSession(); });
var user = new User { PhoneNumber = user.PhoneNumber }; user.Apps.Add(appCode, new ActiveApp { FirstName = "عبدالصالح", LastName = "کاشانی", UserName = abdossaleh, IsActive = true, RolesId = new List<string>{"Admin"} }); await _documentSession.StoreAsync(user); await _documentSession.SaveChangesAsync()
var user = await _documentSession.LoadAsync<User>("Users/131-A");
_documentSession.Advanced.Patch<User, string>("Users/131-A", u => u.PhoneNumber , "09131110000");
_documentSession.Advanced.Patch<User, string>("Users/131-A", u => u.Apps["59"].RolesId , r => r.Add("Admin"));
_documentSession.Advanced.Increment<User, int>("Users/131-A", x => x.TestProp, 10);
_documentSession.Advanced.Defer(new PatchCommandData("Users/131-A", null, new PatchRequest() { Script = $@"this.Apps[args.appCode] = args.app", Values = { {"appCode", appCode}, {"app", new ActiveApp { FirstName = "عبدالصالح", LastName = "کاشانی", UserName = abdossaleh, RolesId = new List<string>{"Admin"} } } } }, null));
Script = "this.Apps[args.app].Roles.splice(args.index,0,args.role)", Values = { { "index": 1 // مکانی که میخواهیم عملیات انجام شود "app", 59 "role", "User" } }
splice(args.index,1,args.role)
Script = @"this.Roles= this.Apps[args.app].Roles.filter(role=> role != args.role);", Values = { {"app", 59} {"role", "User"} }
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.0.0
- name: Setup NuGet
uses: nuget/setup-nuget@v1.0.2
- name: Restore NuGet Packages
run: nuget restore src/WebApplication1.sln
- name: Build (Release)
run: msbuild src/WebApplication1.sln /p:Configuration=Release
- دستور runs-on: windows-latest این workflow را بر روی ویندوز اجرا میکنه (بدیهیه که پروژههای دات نت Full بر روی غیر از ویندوز قابل build شدن نیست)
- در step دوم MSBuild رو توسط اکشن microsoft/setup-msbuild نصب میکنیم.
- در step سوم Nuget رو توسطاکشن nuget/setup-nuget نصب میکنیم.
- در step چهارم وابستگی (پکیج)های پروژه رو restore میکنیم.
- در step چهارم پروژه رو توسط msbuild و در مود Release بیلد میکنیم.
- عبارت src/WebApplication1.sln به پوشه و نام فایل سلوشن شما اشاره میکنه.
- name: Setup VSTest uses: Malcolmnixon/Setup-VSTest@v2 - name: VSTest run: vstest.console ClassLibrary.Test\bin\Debug\ClassLibrary.Test.dll
قبل از شروع، یک خبر!
VsDoc for jQuery 1.3.1 (جهت فعال سازی intellisense آخرین نگارش جی کوئری در VS.Net)
اگر سعی کنید jQuery را به همراه سایر کتابخانههای جاوا اسکریپتی دیگر به صورت همزمان استفاده کنید (مثلا mootools یا ASP.Net Ajax و امثال آن)، احتمالا قسمتی و یا تمامی کدهای جاوا اسکریپتی شما کار نخواهند کرد. برای مثال update panel شما در ASP.Net Ajax از کار میافتد، یا کدهای mootools شما دیگر کار نمیکنند. علت اینجا است که تمامی این کتابخانهها از نشانه $ به عنوان متغیری عمومی که بیانگر نام مستعار کتابخانه مربوطه است استفاده میکنند و در نهایت تمام اینها با هم تداخل خواهند کرد.
خوشبختانه jQuery امکان رفع این تداخل را پیش بینی کرده است که به صورت زیر میباشد:
<script type="text/javascript" language="javascript" src="jquery.min.js"></script>
<script type="text/javascript">
jQuery.noConflict();
jQuery(document).ready(function($) {
//tip-1
$("select > option").each(function() {
var obj = $(this);
obj.attr("title", obj.attr("value"));
});
//tip-1
});
</script>
در اینجا ابتدا jQuery.noConflict فراخوانی شده و سپس document ready متداول هم باید اندکی مطابق کد فوق تغییر کند. مابقی کدهای شما از این پس نیازی به تغییر نخواهند داشت. (روشهای دیگری هم برای تغییر نام $ وجود دارند که در مستندات مربوطه قابل مشاهده است)
ORM های NHibernate و Entity framework روشهای متفاوتی را برای به روز رسانی کلید خارجی با حداقل رفت و برگشت به دیتابیس ارائه میدهند که در ادامه معرفی خواهند شد.
صورت مساله:
فرض کنید میخواهیم برنامهای را بنویسیم که ریز پرداختهای روزانهی ما را ثبت کند. برای اینکار حداقل به یک جدول گروههای اقلام خریداری شده، یک جدول حسابهای تامین کنندهی مخارج، یک جدول فروشندهها و نهایتا یک جدول صورتحسابهای پرداختی بر اساس جداول ذکر شده نیاز خواهد بود.
الف) بررسی مدل برنامه
در اینجا جهت تعریف ویژگیها یا Attributes تعریف شده در این کلاسها از NHibernate validator استفاده شده (+). مزیت اینکار هم علاوه بر اعتبارسنجی سمت کلاینت (پیش از تبادل اطلاعات با بانک اطلاعاتی)، تولید جداولی با همین مشخصات است. برای مثال Fluent NHibernate بر اساس ویژگی Length تعریف شده با طول حداکثر 120 ، یک فیلد nvarchar با همین طول را ایجاد میکند.
public class Account
{
public virtual int Id { get; set; }
[NotNullNotEmpty]
[Length(Min = 3, Max = 120, Message = "طول نام باید بین 3 و 120 کاراکتر باشد")]
public virtual string Name { get; set; }
}
public class Category
{
public virtual int Id { get; set; }
[NotNullNotEmpty]
[Length(Min = 3, Max = 130, Message = "طول نام باید بین 3 و 130 کاراکتر باشد")]
public virtual string Name { get; set; }
}
public class Payee
{
public virtual int Id { get; set; }
[NotNullNotEmpty]
[Length(Min = 3, Max = 120, Message = "طول نام باید بین 3 و 120 کاراکتر باشد")]
public virtual string Name { get; set; }
}
public class Bill
{
public virtual int Id { get; set; }
[NotNull]
public virtual Account Account { get; set; }
[NotNull]
public virtual Category Category { get; set; }
[NotNull]
public virtual Payee Payee { get; set; }
[NotNull]
public virtual decimal Amount { set; get; }
[NotNull]
public virtual DateTime BillDate { set; get; }
[NotNullNotEmpty]
[Length(Min = 1, Max = 500, Message = "طول توضیحات باید بین 1 و 500 کاراکتر باشد")]
public virtual string Description { get; set; }
}
ب) ساختار جداول متناظر (تولید شده به صورت خودکار توسط Fluent NHibernate در اینجا)
در مورد نحوهی استفاده از ویژگی AutoMapping و همچنین تولید خودکار ساختار بانک اطلاعاتی از روی جداول در NHibernate قبلا توضیح داده شده است. البته بدیهی است که ترکیب مقالهی Validation و آشنایی با AutoMapping در اینجا جهت اعمال ویژگیها باید بکار گرفته شود که در همان مقالهی Validation مفصل توضیح داده شده است.
نکتهی مهم database schema تولیدی، کلیدهای خارجی (foreign key) تعریف شده بر روی جدول Bills است (همان AccountId، CategoryId و PayeeId تعریف شده) که به primary key جداول متناظر اشاره میکند.
create table Accounts (
AccountId INT IDENTITY NOT NULL,
Name NVARCHAR(120) not null,
primary key (AccountId)
)
create table Bills (
BillId INT IDENTITY NOT NULL,
Amount DECIMAL(19,5) not null,
BillDate DATETIME not null,
Description NVARCHAR(500) not null,
AccountId INT not null,
CategoryId INT not null,
PayeeId INT not null,
primary key (BillId)
)
create table Categories (
CategoryId INT IDENTITY NOT NULL,
Name NVARCHAR(130) not null,
primary key (CategoryId)
)
create table Payees (
PayeeId INT IDENTITY NOT NULL,
Name NVARCHAR(120) not null,
primary key (PayeeId)
)
alter table Bills
add constraint fk_Account_Bill
foreign key (AccountId)
references Accounts
alter table Bills
add constraint fk_Category_Bill
foreign key (CategoryId)
references Categories
alter table Bills
add constraint fk_Payee_Bill
foreign key (PayeeId)
references Payees
ج) صفحهی ثبت صورتحسابها
صفحات ثبت گروههای اقلام، حسابها و فروشندهها، نکتهی خاصی ندارند. چون این جداول وابستگی خاصی به جایی نداشته و به سادگی اطلاعات آنها را میتوان ثبت یا به روز کرد.
صفحهی مشکل در این مثال، همان صفحهی ثبت صورتحسابها است که از سه کلید خارجی به سه جدول دیگر تشکیل شده است.
عموما برای طراحی این نوع صفحات، کلیدهای خارجی را با drop down list نمایش میدهند و اگر در جهت سهولت کار کاربر قدم برداشته شود، باید از یک Auto complete drop down list استفاده کرد تا کاربر برنامه جهت یافتن آیتمهای از پیش تعریف شده کمتر سختی بکشد.
اگر از Silverlight یا WPF استفاده شود، امکان بایند یک لیست کامل از اشیاء با تمام خواص مرتبط به آنها وجود دارد (هر رکورد نمایش داده شده در دراپ داون لیست، دقیقا معادل است با یک شیء متناظر با کلاسهای تعریف شده است). اگر از ASP.NET استفاده شود (یعنی یک محیط بدون حالت که پس از نمایش یک صفحه دیگر خبری از لیست اشیاء بایند شده وجود نخواهد داشت و همگی توسط وب سرور جهت صرفه جویی در منابع تخریب شدهاند)، بهتر است datatextfield را با فیلد نام و datavaluefield را با فیلد Id مقدار دهی کرد تا کاربر نهایی، نام را جهت ثبت اطلاعات مشاهده کند و برنامه از Id موجود در لیست جهت ثبت کلیدهای خارجی استفاده نماید.
و نکتهی اصلی هم همینجا است که چگونه؟! چون ما زمانیکه با یک ORM سر و کار داریم، برای ثبت یک رکورد در جدول Bills باید یک وهله از کلاس Bill را ایجاد کرده و خواص آنرا مقدار دهی کنیم. اگر به تعریف کلاس Bill مراجعه کنید، سه خاصیت آن از نوع سه کلاس مجزا تعریف شده است. به به عبارتی با داشتن فقط یک id از رکوردهای این کلاسها باید بتوان سه وهلهی متناظر آنها را از بانک اطلاعاتی خواند و سپس به این خواص انتساب داد:
var newBill = new Bill
{
Account = accountRepository.GetByKey(1),
Amount = 1,
BillDate = DateTime.Now,
Category = categoryRepository.GetByKey(1),
Description = "testestest...",
Payee = payeeRepository.GetByKey(1)
};
- یکبار برای دریافت رکورد متناظر با گروهها بر اساس کلید اصلی آن (که از دراپ داون لیست مربوطه دریافت میشود)
- یکبار برای دریافت رکورد متناظر با فروشندهها بر اساس کلید اصلی آن (که از دراپ داون لیست مربوطه دریافت میشود)
- یکبار برای دریافت رکورد متناظر با حسابها بر اساس کلید اصلی آن (که از دراپ داون لیست مربوطه دریافت میشود)
- یکبار هم ثبت نهایی اطلاعات در بانک اطلاعاتی
متد GetByKey فوق همان متد session.Get استاندارد NHibernate است (چون به primary key ها از طریق drop down list دسترسی داریم، به سادگی میتوان بر اساس متد Get استاندارد ذکر شده عمل کرد).
SQL نهایی تولیدی هم به صورت واضحی این مشکل را نمایش میدهد (4 بار رفت و برگشت؛ سه بار select یکبار هم insert نهایی):
SELECT account0_.AccountId as AccountId0_0_, account0_.Name as Name0_0_
FROM Accounts account0_ WHERE account0_.AccountId=@p0;@p0 = 1 [Type: Int32 (0)]
SELECT category0_.CategoryId as CategoryId2_0_, category0_.Name as Name2_0_
FROM Categories category0_ WHERE category0_.CategoryId=@p0;@p0 = 1 [Type: Int32 (0)]
SELECT payee0_.PayeeId as PayeeId3_0_, payee0_.Name as Name3_0_
FROM Payees payee0_ WHERE payee0_.PayeeId=@p0;@p0 = 1 [Type: Int32 (0)]
INSERT INTO Bills (Amount, BillDate, Description, AccountId, CategoryId, PayeeId)
VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
select SCOPE_IDENTITY();
@p0 = 1 [Type: Decimal (0)],
@p1 = 2010/12/27 11:48:33 ق.ظ [Type: DateTime (0)],
@p2 = 'testestest...' [Type: String (500)],
@p3 = 1 [Type: Int32 (0)],
@p4 = 1 [Type: Int32 (0)],
@p5 = 1 [Type: Int32 (0)]
کسانی که قبلا با رویههای ذخیره شده کار کرده باشند (stored procedures) احتمالا الان خواهند گفت؛ ما که گفتیم این روش کند است! سربار زیادی دارد! فقط کافی است یک SP بنویسید و کل عملیات را با یک رفت و برگشت انجام دهید.
اما در ORMs نیز برای انجام این مورد در طی یک حرکت یک ضرب راه حلهایی وجود دارد که در ادامه بحث خواهد شد:
د) پیاده سازی با NHibernate
برای حل این مشکل در NHibernate با داشتن primary key (برای مثال از طریق datavaluefield ذکر شده)، بجای session.Get از session.Load استفاده کنید.
session.Get یعنی همین الان برو به بانک اطلاعاتی مراجعه کن و رکورد متناظر با کلید اصلی ذکر شده را بازگشت بده و یک شیء از آن را ایجاد کن (حالتهای دیگر دسترسی به اطلاعات مانند استفاده از LINQ یا Criteria API یا هر روش مشابه دیگری نیز در اینجا به همین معنا خواهد بود).
session.Load یعنی فعلا دست نگه دار! مگر در جدول نهایی نگاشت شده، اصلا چیزی به نام شیء مثلا گروه وجود دارد؟ مگر این مورد واقعا یک فیلد عددی در جدول Bills بیشتر نیست؟ ما هم که الان این عدد را داریم (به کمک عناصر دراپ داون لیست)، پس لطفا در پشت صحنه یک پروکسی برای ایجاد شیء مورد نظر ایجاد کن (uninitialized proxy to the entity) و سپس عملیات مرتبط را در حین تشکیل SQL نهایی بر اساس این عدد موجود انجام بده. یعنی نیازی به رفت و برگشت به بانک اطلاعاتی نیست. در این حالت اگر SQL نهایی را بررسی کنیم فقط یک سطر زیر خواهد بود (سه select ذکر شده حذف خواهند شد):
INSERT INTO Bills (Amount, BillDate, Description, AccountId, CategoryId, PayeeId)
VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
select SCOPE_IDENTITY();
@p0 = 1 [Type: Decimal (0)],
@p1 = 2010/12/27 11:58:22 ق.ظ [Type: DateTime (0)],
@p2 = 'testestest...' [Type: String (500)],
@p3 = 1 [Type: Int32 (0)],
@p4 = 1 [Type: Int32 (0)],
@p5 = 1 [Type: Int32 (0)]
ه) پیاده سازی با Entity framework
Entity framework زمانیکه بانک اطلاعاتی فوق را (به روش database first) به کلاسهای متناظر تبدیل/نگاشت میکند، حاصل نهایی مثلا در مورد کلاس Bill به صورت خلاصه به شکل زیر خواهد بود:
public partial class Bill : EntityObject
{
public global::System.Int32 BillId {set;get;}
public global::System.Decimal Amount {set;get;}
public global::System.DateTime BillDate {set;get;}
public global::System.String Description {set;get;}
public global::System.Int32 AccountId {set;get;}
public global::System.Int32 CategoryId {set;get;}
public global::System.Int32 PayeeId {set;get;}
public Account Account {set;get;}
public Category Category {set;get;}
}
قبل از بررسی این پنل اجازه دهید نگاهی به تعریف DOM بیندازیم.
DOM چیست؟
مدل شیءگرای سند یا دام (DOM - Document Object Model) عنوان یکی از دو ساختوارۀ (architecture) اصلی است (در کنار اساِیاکس) که بر اساس آن سندهای اکسامال را به اشیایی که در بردارندهٔ آن است، تجزیه نموده، و آنها را بهصورت یک ساختار درختی دادهها در فضای حافظه اصلی پهن میکند. ساختوارۀ دام، نه به زبان برنامهنویسی خاصّی وابستگی دارد و نه به سکّوی برنامهنویسی ویژهای، بلکه، به منظور اجراء و پیادهسازی آن باید از یک زبان برنامهنویسی بلندتراز همچون جاوا، سیشارپ، جاوااسکریپت یا مشابه آنها سود بجوییم. آنسوی رابط کاربر سند با مدلی شیءگرا نمایانده میشود.
Options Menu
این منو با راست کلیک کردن بروی نام پنل یا کلیک کردن بروی مثلثی که روی پنل قرار دارد، نمایش داده میشود.
- Show User-defined Properties
در صورت فعال بودن، پراپرتی هایی که توسط کاربر به صفحه اضافه شده اند را نمایش میدهد. - Show User-defined Functions
در صورت فعال بودن، توابعی که توسط کاربر به صفحه اضافه شده اند را نمایش میدهد.
- Show DOM Properties
در صورت فعال بودن، پراپرتی هایی که بصورت پیشفرض در DOM وجود دارند را نمایش میدهد. - Show DOM Functions
در صورت فعال بودن، توابعی که بصورت پیشفرض در DOM وجود دارند را نمایش میدهد. - Show DOM Constants
در صورت فعال بودن، const هایی که بصورت پیشفرض در DOM وجود دارند را نمایش میدهد. - Show Inline Event Handlers
در صورت فعال بودن، رویدادهایی که بصورت خطی در تگها تعریف شده اند را نمایش میدهد. - Show Closures
در صورت فعال بودن، Closureها را نمایش میدهد. - Show Own Properties Only
در صورت فعال بودن، فقط پراپرتی هایی که بروی خود شئ تعریف شده اند را نمایش میدهد. - Show Enumerable Properties Only
در صورت فعال بودن، فقط پراپرتیهای شمارشی را نمایش میدهد. - Refresh
محتویات پنل را بروزرسانی میکند.
Property Path
این قسمت در بالاترین بخش پنل قرار دارد و وظیفهی آن نمایش مسیر شئ از خود شئ تا window است.
همچنین با راست کلیک کردن بروی این قسمت دو گزینه نمایش داده میشود. Refresh برای بروزسازی آدرس نمایش داده شده و Use in Command Line هم برای استفاده از شئ در خط فرمان است. پس از راست کلیک کردن بروی یک شئ و انتخاب گزینهی Use in Command Line فایرباگ تمرکز برنامه را به خط فرمان منتقل میکند و شئ را تحت متغییری به نام $p در خط فرمان کپی میکند.
رنگ ها
برای مشخص کردن نوع متغییرهای این پنل، فایرباگ برای هر نوع متغییر از یک رنگ استفاده میکند.
اشیاء ، اشیاء DOM ، توابع Getter ، توابع تعریفی کاربر ، توابع DOM ، توابع Constructor ، پراپرتیهای Read-only
Auto-Completion
مشابه پنلهای Console, CSS, HTML در این پنل هم امکان اعمال تغییرات همراه با قابلیت تکمیل خودکار وجود دارد.
localStorage
در HTML5 سیستمی برای ذخیره مقادیر در سمت کاربر، به نام localStorage معرفی شد. در این پنل میتوانید محتویات آن را بررسی/ویرایش کنید. برای کار با توابع آن هم میتوانید از پنل Console استفاده کنید. ( همچنین میتوانید از پنل Console بصورت Popup در این پنل و پنلهای دیگر هم استفاده کنید. به تصویر زیر توجه فرمایید. )
توجه کنید که برای مشاهدهی این شئ، باید گزینهی Show DOM Properties فعال باشد و همیچنین در Property Path، شئ window فعال باشد.
Breakpoint Column
شما میتوانید با کلیک بروی ستون سمت چپ پراپرتی ها، آن پراپرتی را تحت نظر گرفته و در صورت تغییر یافتن مقدار آن پراپرتی، کنترل روند اجرای برنامه از همان نقطه را بدست بگیرید. به این صورت که زمانی که کدی پراپرتی موردنظر را تغییر دهد، پنل Script روند اجرای کد را در همان قسمت متوقف میکند.
( ممکن است فایرباگ بصورت خودکار به پنل Script سوئیچ نکند و بعد از متوقف شدن برنامه هم اگر به پنل Script سوئیچ کنید نتیجه را نبینید. پس بهتر است قبل از تغییر یافتن پراپرتی مورد نظر و بعد از قرار دادن Breakpoint به پنل Script بروید. )
Context Menu
با راست کلیک کردن در قسمتهای مختلف پنل، منوهای متفاوتی را خواهید دید. همچنین با راست کلیک کردن بروی مقادیر پراپرتی ها، منوی متناسب با آن مقدار را خواهید دید. مثلا اگر بروی یک تگ HTML در این پنل راست کلیک کنید، منویی که خواهید دید همان منویی است که در پنل HTML مشاهده میکردید.
گزینه | Context | توضیحات |
Copy Name | Property List | نام پراپرتی را در حافظه کپی میکند. |
Copy Path | Property List | آدرس پراپرتی را در حافظه کپی میکند. |
Copy Value | String and Number values | محتوای پراپرتی را در حافظه کپی میکند. |
Edit Property... | Property List ( پراپرتی و توابع کاربر ) | پراپرتی را به حالت ویرایش میآورد. |
Delete Property | Property List ( پراپرتی و توابع کاربر ) | پراپرتی را حذف میکند. |
Break On Property Change | Property List ( پراپرتی و توابع کاربر ) | مشابه پاراگرف قبلی. |
Refresh | Property List, Property Path | محتویات پنل را بروزرسانی میکند. |
برای توابع هم دو منوی اضافی وجود دارد:
- Log Calls to "<function name>"
فراخوانیهای تابع مورد نظر را Log میکند. ( برای توضیحات بیشتر دستور monitor که از توابع خط فرمان است را ملاحظه بفرمایید. ) - Copy Function
نام و بندهی تابع را در حافظه کپی میکند.
downloadOrder(orderId: number , userId : string) { this._http.get(this._config.getApiURI() + '/Download/productfile/' + orderId + '/' + userId, { observe: 'blob'}) .subscribe( (data) => { if(this._functionService.isNullOrEmpty(data.body)){ this._snackBarService.error('فایل پیدا نشد'); return; } var contentType = data.body.type || "application/octet-stream"; var fileInfo = JSON.parse(data.headers.get('FileInfo')); var blob = new Blob([data.body], { type: contentType }); var url = window.URL.createObjectURL(blob); var anchor = document.createElement("a"); anchor.setAttribute("href", url) anchor.setAttribute("download", fileInfo.fileName + fileInfo.fileType); anchor.setAttribute("target", "_blank"); document.body.appendChild(anchor); anchor.click(); setTimeout(function () { document.body.removeChild(anchor); window.URL.revokeObjectURL(url); }, 200); }, error => { this._snackBarService.error(error); }); }
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0) { await Response.Body.WriteAsync(buffer); //return File(buffer, GetContentType(fullPath), newFileName, true); //return new FileStreamResult(stream,"application/octet-stream"); }
در این قسمت قصد داریم یک فرم Ajaxایی را در ASP.NET MVC به همراه تمام مسایل اعتبارسنجی، پردازش اطلاعات و غیره را به کمک Twitter Bootstrap و jQuery Ajax پیاده سازی کنیم.
تهیه افزونه jquery.bootstrap-modal-ajax-form.js
از این جهت که مباحث مرتبط با نمایش و پردازش فرمهای مودال Ajaxایی به کمک Twitter Bootstrap اندکی نکته دار و طولانی هستند، بهتر است این موارد را به شکل یک افزونه، کپسوله کنیم. کدهای کامل افزونه jquery.bootstrap-modal-ajax-form.js را در ادامه ملاحظه میکنید:
// <![CDATA[ (function ($) { $.bootstrapModalAjaxForm = function (options) { var defaults = { renderModalPartialViewUrl: null, renderModalPartialViewData: null, postUrl: '/', loginUrl: '/login', beforePostHandler: null, completeHandler: null, errorHandler: null }; var options = $.extend(defaults, options); var validateForm = function (form) { //فعال سازی دستی اعتبار سنجی جیکوئری var val = form.validate(); val.form(); return val.valid(); }; var enableBootstrapStyleValidation = function () { $.validator.setDefaults({ highlight: function (element, errorClass, validClass) { if (element.type === 'radio') { this.findByName(element.name).addClass(errorClass).removeClass(validClass); } else { $(element).addClass(errorClass).removeClass(validClass); $(element).closest('.control-group').removeClass('success').addClass('error'); } $(element).trigger('highlited'); }, unhighlight: function (element, errorClass, validClass) { if (element.type === 'radio') { this.findByName(element.name).removeClass(errorClass).addClass(validClass); } else { $(element).removeClass(errorClass).addClass(validClass); $(element).closest('.control-group').removeClass('error').addClass('success'); } $(element).trigger('unhighlited'); } }); } var enablePostbackValidation = function () { $('form').each(function () { $(this).find('div.control-group').each(function () { if ($(this).find('span.field-validation-error').length > 0) { $(this).addClass('error'); } }); }); } var processAjaxForm = function (dialog) { $('form', dialog).submit(function (e) { e.preventDefault(); if (!validateForm($(this))) { //اگر فرم اعتبار سنجی نشده، اطلاعات آن ارسال نشود return false; } //در اینجا میتوان مثلا دکمهای را غیرفعال کرد if (options.beforePostHandler) options.beforePostHandler(); //اطلاعات نباید کش شوند $.ajaxSetup({ cache: false }); $.ajax({ url: options.postUrl, type: "POST", data: $(this).serialize(), success: function (result) { if (result.success) { $('#dialogDiv').modal('hide'); if (options.completeHandler) options.completeHandler(); } else { $('#dialogContent').html(result); if (options.errorHandler) options.errorHandler(); } } }); return false; }); }; var mainContainer = "<div id='dialogDiv' class='modal hide fade in'><div id='dialogContent'></div></div>"; enableBootstrapStyleValidation(); //اعمال نکات خاص بوت استرپ جهت اعتبارسنجی یکپارچه با آن $.ajaxSetup({ cache: false }); $.ajax({ type: "POST", url: options.renderModalPartialViewUrl, data: options.renderModalPartialViewData, contentType: "application/json; charset=utf-8", dataType: "json", complete: function (xhr, status) { var data = xhr.responseText; var data = xhr.responseText; if (xhr.status == 403) { window.location = options.loginUrl; //در حالت لاگین نبودن شخص اجرا میشود } else if (status === 'error' || !data) { if (options.errorHandler) options.errorHandler(); } else { var dialogContainer = "#dialogDiv"; $(dialogContainer).remove(); $(mainContainer).appendTo('body'); $('#dialogContent').html(data); // دریافت پویای اطلاعات مودال دیالوگ $.validator.unobtrusive.parse("#dialogContent"); // فعال سازی اعتبارسنجی فرمی که با ایجکس بارگذاری شده enablePostbackValidation(); // و سپس نمایش آن به صورت مودال $('#dialogDiv').modal({ backdrop: 'static', //با کلیک کاربر روی صفحه، صفحه مودال بسته نمیشود keyboard: true }, 'show'); // تحت نظر قرار دادن این فرم اضافه شده processAjaxForm('#dialogContent'); } } }); }; })(jQuery); // ]]>
- توابع enableBootstrapStyleValidation و enablePostbackValidation در مطلب «اعمال کلاسهای ویژه اعتبارسنجی Twitter bootstrap به فرمهای ASP.NET MVC» بررسی شدند.
- این افزونه با توجه به مقدار renderModalPartialViewUrl، یک partial view را از برنامه ASP.NET MVC درخواست میکند.
- سپس این partial view را به صورت خودکار به صفحه اضافه کرده و آنرا به صورت modal نمایش میدهد.
- پس از افزودن فرم Ajaxایی دریافتی، مسایل اعتبارسنجی را به آن اعمال کرده و سپس دکمه submit آنرا تحت کنترل قرار میدهد.
- در زمان submit، ابتدا بررسی میکند که آیا فرم معتبر است و اعتبارسنجی آن بدون مشکل است؟ اگر اینچنین است، اطلاعات فرم را به آدرس postUrl به صورت Ajaxایی ارسال میکند.
کدهای مدل برنامه
using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace Mvc4TwitterBootStrapTest.Models { public class User { public int Id { set; get; } [DisplayName("نام")] [Required(ErrorMessage="لطفا نام را تکمیل کنید")] public string Name { set; get; } [DisplayName("نام خانوادگی")] [Required(ErrorMessage = "لطفا نام خانوادگی را تکمیل کنید")] public string LastName { set; get; } } }
کدهای کنترلر برنامه
using System.Web.Mvc; using Mvc4TwitterBootStrapTest.Models; namespace Mvc4TwitterBootStrapTest.Controllers { public class ModalFormAjaxController : Controller { [HttpGet] public ActionResult Index() { return View(); //نمایش صفحه اولیه } [HttpPost] //برای این حالت امنتر است //[AjaxOnly] public ActionResult RenderModalPartialView() { //رندر پارشال ویوو صفحه مودال به همراه اطلاعات مورد نیاز آن return PartialView(viewName: "_ModalPartialView", model: new User { Name = "", LastName = "" }); } [HttpPost] //[AjaxOnly] public ActionResult Index(User user) //ذخیره سازی اطلاعات { if (this.ModelState.IsValid) { //todo: SaveChanges; return Json(new { success = true }); } this.ModelState.AddModelError("", "خطایی رخ داده است"); return PartialView("_ModalPartialView", user); } } }
الف) متد Index حالت HttpGet که صفحه ابتدایی را نمایش خواهد داد.
ب) متد RenderModalPartialView یک partial view اضافه شده به برنامه به نام _ModalPartialView.cshtml را بازگشت میدهد. این partial view در حقیقت همان فرمی است که قرار است به صورت مودال نمایش داده شود و پردازش آن نیز Ajaxایی باشد.
ج) متد Index حالت HttpPost که نهایتا اطلاعات فرم مودال را دریافت خواهد کرد. اگر پردازش موفقیت آمیز بود، نیاز است همانند کدهای فوق return Json صورت گیرد. در غیراینصورت مجددا همان partial view را بازگشت دهید.
کدهای Index.cshtml
@{ ViewBag.Title = "Index"; var renderModalPartialViewUrl = Url.Action("RenderModalPartialView", "ModalFormAjax"); var postDataUrl = Url.Action("Index", "ModalFormAjax"); } <h2> Index</h2> <a href="#" class="btn btn-primary" id="btnCreate">ثبت اطلاعات</a> @section JavaScript { <script type="text/javascript"> $(function () { $('#btnCreate').click(function (e) { e.preventDefault(); //میخواهیم لینک به صورت معمول عمل نکند $.bootstrapModalAjaxForm({ postUrl: '@postDataUrl', renderModalPartialViewUrl: '@renderModalPartialViewUrl', renderModalPartialViewData: {}, loginUrl: '/login', beforePostHandler: function () { }, completeHandler: function () { // Refresh: برای حالتیکه نیاز به به روز رسانی کامل صفحه زیرین باشد // location.reload(); }, errorHandler: function () { } }); }); }); </script> }
- در اینجا یک لینک ساده در صفحه قرار گرفته و به کمک کلاس btn مجموعه bootstrap به شکل یک دکمه مزین شده است.
- در ادامه نحوه استفاده از افزونهای را که در ابتدای بحث طراحی کردیم، ملاحظه میکنید. کار با آن بسیار ساده است و تنها باید مسیرهای ارسال اطلاعات نهایی به سرور یا postDataUrl و مسیر دریافت partial view رندر شده یا renderModalPartialViewUrl به آن معرفی شود. سایر مسایل آن خودکار است.
کدهای _ModalPartialView.cshtml یا همان فرم مودال برنامه
@model Mvc4TwitterBootStrapTest.Models.User <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"> ×</button> <h5> افزودن کاربر جدید</h5> </div> @using (Html.BeginForm("Index", " ModalFormAjax", FormMethod.Post, new { @class = "modal-form" })) { <div class="modal-body"> @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" }) <fieldset class="form-horizontal"> <legend>مشخصات کاربر</legend> <div class="control-group"> @Html.LabelFor(model => model.Name, new { @class = "control-label" }) <div class="controls"> @Html.EditorFor(model => model.Name) @Html.ValidationMessageFor(model => model.Name, null, new { @class = "help-inline" }) </div> </div> <div class="control-group"> @Html.LabelFor(model => model.LastName, new { @class = "control-label" }) <div class="controls"> @Html.EditorFor(model => model.LastName) @Html.ValidationMessageFor(model => model.LastName, null, new { @class = "help-inline" }) </div> </div> </fieldset> </div> <div class="modal-footer"> <button class="btn btn-primary" type="submit"> ارسال</button> <button class="btn" data-dismiss="modal" aria-hidden="true"> انصراف</button> </div> }
حاصل نهایی این مبحث را در دو شکل ذیل ملاحظه میکنید. صفحه index نمایش دهنده یک دکمه و در ادامه باز شدن یک فرم مودال، پس از کلیک بر روی دکمه ثبت اطلاعات.
DbContext pooling در EF Core 2.0
ظاهرا از این روش فقط زمانی میشه استفاده کرد که کلاس Context برنامه فقط یک سازنده با پارامتر ورودی DbContextOptions داشته باشه.آیا راه حلی برای این موضوع هست ؟
خلاصهای کوتاه در مورد WinRT
- هدفم از پاسخ فوق، اشاره به پیش زمینهی ذهنی بود که هم اکنون سبب حذف امکان وجود فراخوانیهای همزمان شده است؛ مانند: انتخاب فایل ، کار با وب کم
این نوع کدها برای برنامه نویسهای سیلورلایت بسیار آشنا هستند.