نظرات مطالب
بررسی دقیقتر صفحات آبی ویندوز
سلام آقای نصیری... اولا خیلی خوشحالم که شما هم بلاگری هستید :دی
مطلب خیلی جالبی انتخاب کردید... از اونجایی که توی کتاب هم نوشته اگه با BSD رو به رو شدید زنگ بزنیدsupport معمولا توی ایران کسی دنبال این قضیه نمی ره. چند ماه پیش یکی از سرور های شرکت دوستم که حالا اتفاقا شرکت معروفی هم هست مرتبا با این مشکل رو به رو می شد و تمامی دانشمندان نمی توانستند این مشکل رو حل کنن! آخرش من از فروم Technet جواب شو پیدا کردم هر چند که دیگه اون سرور رو جایگزین کرده بودن اما خود بالاخره اون سرور هم به سر کارش برگشت دوباره.
اما تفسیر این گزارشات گاهی خیلی پیچیده می شه و واقعا مثل log monitoring و یا performance monitoring مرد می خواهد و حوصله که بتوانه این ها رو تفسیر کنه!
مطلب خیلی جالبی انتخاب کردید... از اونجایی که توی کتاب هم نوشته اگه با BSD رو به رو شدید زنگ بزنیدsupport معمولا توی ایران کسی دنبال این قضیه نمی ره. چند ماه پیش یکی از سرور های شرکت دوستم که حالا اتفاقا شرکت معروفی هم هست مرتبا با این مشکل رو به رو می شد و تمامی دانشمندان نمی توانستند این مشکل رو حل کنن! آخرش من از فروم Technet جواب شو پیدا کردم هر چند که دیگه اون سرور رو جایگزین کرده بودن اما خود بالاخره اون سرور هم به سر کارش برگشت دوباره.
اما تفسیر این گزارشات گاهی خیلی پیچیده می شه و واقعا مثل log monitoring و یا performance monitoring مرد می خواهد و حوصله که بتوانه این ها رو تفسیر کنه!
مطالب
SFDown
چند روز قبل جهت دریافت فایلهای تنظیم سطح دوم کش NHibernate به سایت سورس فورج مراجعه کردم و ... آه از نهادم برخاست! نه از این جهت که این سایت مدت مدیدی است ما رو تحریم کرده، به این دلیل که سورس فورج حتی با IP غیر ایرانی تونسته بود موقعیت من رو شناسایی کنه. شبیه به همین مورد مدتی است توسط گوگل نیز بکارگرفته میشه. به نظر میرسه این وسط جایی نشتی وجود داره. برای مثال در فایرفاکس امکان گزارش Geo Location به صورت پیش فرض فعال است. هر چند در مستندات آن صراحتا عنوان شده که ... خیر ... ما این اطلاعات را بدون تائید شما بروز نمیدهیم؛ ولی سؤال اینجا است که چطور تونستند از روی IP غیرایرانی، موقعیت من رو تشخیص دهند؟!
غیر فعال کردن این مورد هم ساده است. مطابق تصویر زیر عمل کنید:
به عبارتی در نوار آدرس فایرفاکس عبارت about:config را تایپ کرده، سپس عبارت geo.enabled را یافته و غیرفعال کنید. اکنون نیاز است یکبار مرورگر را بسته و باز کنید.
و ... IP ایی که سوخت ... تا یکی دو هفته عمل نخواهند کرد و بعد از لیست سیاه پاک خواهد شد.
برای رفع این نقیصه و سادهتر کردن دریافت فایلها از سورج فورج، برنامهی کوچکی را تهیه کردهام که با گرفتن آدرس یک پروژه سورس فورج، لینک مستقیم قابل دریافت فایلهای آنرا در اختیار شما قرار میدهد. این برنامه بر اساس Mirror های سورس فورج عمل میکند و به صورت خودکار تمام آنها را بررسی کرده و مورد قابل استفاده را گزارش خواهد داد. یا میتوانید توسط خود برنامه فایل نهایی را دریافت کنید یا امکان کپی کردن یا ذخیره کردن لینکهای مستقیم نهایی یافت شده، در برنامه پیش بینی شده است (جهت دریافت توسط برنامه download manager مورد علاقه شما).
بازخوردهای دوره
بوت استرپ (نگارش 3) چیست؟
یک نکته تکمیلی در مورد فایل respond.min.js
این فایل را اگر به صورت معمولی در یک صفحه html بارگذاری شده از فایل سیستم اجرا کنید، پیام access is denied را دریافت خواهید کرد. علت این است که درخواستهای xmlHttpRequest آن برای اجرا نیاز به وب سرور دارند. بنابراین برای مواجه نشدن با این خطا، بهتر است مثال را در یک برنامه معمولی ASP.NET آزمایش کنید.
این فایل را اگر به صورت معمولی در یک صفحه html بارگذاری شده از فایل سیستم اجرا کنید، پیام access is denied را دریافت خواهید کرد. علت این است که درخواستهای xmlHttpRequest آن برای اجرا نیاز به وب سرور دارند. بنابراین برای مواجه نشدن با این خطا، بهتر است مثال را در یک برنامه معمولی ASP.NET آزمایش کنید.
یکی از مزایای کار با ORMها، امکان تعویض نوع بانک اطلاعاتی برنامه، بدون نیازی به تغییری در کدهای برنامه است. برای مثال فرض کنید میخواهید با تغییر رشتهی اتصالی برنامه، یکبار از بانک اطلاعاتی SQL Server و بار دیگر از بانک اطلاعاتی کاملا متفاوتی مانند SQLite استفاده کنید. در این مطلب نکات استفادهی از چندین نوع بانک اطلاعاتی متفاوت را در برنامههای مبتنی بر EF Core بررسی خواهیم کرد.
هر بانک اطلاعاتی باید Migration و Context خاص خودش را داشته باشد
تامین کنندهی بانکهای اطلاعاتی مختلف، عموما تنظیمات خاص خودشان را داشته و همچنین دستورات SQL متفاوتی را نیز تولید میکنند. به همین جهت نمیتوان از یک تک Context، هم برای SQLite و هم SQL Server استفاده کرد. به علاوه قصد داریم اطلاعات Migrations هر کدام را نیز در یک اسمبلی جداگانه قرار دهیم. در یک چنین حالتی EF نمیپذیرد که Context تولید کنندهی Migration، در اسمبلی دیگری قرار داشته باشد و باید حتما در همان اسمبلی Migration قرار گیرد. بنابراین ساختار پوشه بندی مثال جاری به صورت زیر خواهد بود:
- در پوشهی EFCoreMultipleDb.DataLayer فقط اینترفیس IUnitOfWork را قرار میدهیم. از این جهت که وقتی قرار شد در برنامه چندین Context تعریف شوند، لایهی سرویس برنامه قرار نیست بداند در حال حاضر با کدام Context کار میکند. به همین جهت است که تغییر بانک اطلاعاتی برنامه، تغییری را در کدهای اصلی آن ایجاد نخواهد کرد.
- در پوشهی EFCoreMultipleDb.DataLayer.SQLite کدهای Context و همچنین IDesignTimeDbContextFactory مخصوص SQLite را قرار میدهیم.
- در پوشهی EFCoreMultipleDb.DataLayer.SQLServer کدهای Context و همچنین IDesignTimeDbContextFactory مخصوص SQL Server را قرار میدهیم.
برای نمونه ابتدای Context مخصوص SQLite چنین شکلی را دارد:
و IDesignTimeDbContextFactory مخصوص آن که برای Migrations از آن استفاده میشود، به صورت زیر تهیه خواهد شد:
هدف از این فایل، ساده سازی کار تولید اطلاعات Migrations برای EF Core است. به این صورت ساخت new SQLiteDbContext توسط ما صورت خواهد گرفت و دیگر EF Core درگیر جزئیات وهله سازی آن نمیشود.
تنظیمات رشتههای اتصالی بانکهای اطلاعاتی مختلف
در اینجا محتویات فایل appsettings.json را که در آن تنظیمات رشتههای اتصالی دو بانک SQL Server LocalDB و همچنین SQLite در آن ذکر شدهاند، مشاهده میکنید:
همین رشتهی اتصالی است که در SQLiteDbContextFactory مورد استفاده قرار میگیرد.
یک کلید InUseKey را هم در اینجا تعریف کردهایم تا مشخص باشد در ابتدای کار برنامه، کلید کدام رشتهی اتصالی مورد استفاده قرار گیرد. برای مثال در اینجا کلید رشتهی اتصالی SQLite تنظیم شدهاست.
در این تنظیمات یک DataDirectory را نیز مشاهده میکنید. مقدار آن در فایل Startup.cs برنامه به صورت زیر بر اساس پوشهی جاری تعیین میشود و در نهایت به wwwroot\app_data اشاره خواهد کرد:
دستورات تولید Migrations و به روز رسانی بانک اطلاعاتی
چون تعداد Contextهای برنامه بیش از یک مورد شدهاست، دستورات متداولی را که تاکنون برای تولید Migrations و یا به روز رسانی ساختار بانک اطلاعاتی اجرا میکردید، با پیام خطایی که این مساله را گوشزد میکند، متوقف خواهند شد. راه حل آن ذکر صریح Context مدنظر است:
برای تولید Migrations، از طریق خط فرمان، به پوشهی اسمبلی مدنظر وارد شده و دستور زیر را اجرا کنید:
در اینجا ذکر startup-project و همچنین context برای پروژههایی که context آنها خارج از startup-project است و همچنین بیش از یک context دارند، ضروریاست. بدیهی است این دستورات را باید یکبار در پوشهی EFCoreMultipleDb.DataLayer.SQLite و یکبار در پوشهی EFCoreMultipleDb.DataLayer.SQLServer اجرا کنید.
دو سطر اول آن، زمان اجرای دستورات را به عنوان نام فایلها تولید میکنند.
پس از تولید Migrations، اکنون نوبت به تولید بانک اطلاعاتی و یا به روز رسانی بانک اطلاعاتی موجود است:
در این مورد نیز ذکر startup-project و همچنین context مدنظر ضروری است.
بدیهی است این رویه را پس از هربار تغییراتی در موجودیتهای برنامه و یا تنظیمات آنها در Contextهای متناظر، نیاز است مجددا اجرا کنید. البته اجرای اولین دستور اجباری است؛ اما میتوان دومین دستور را به صورت زیر نیز اجرا کرد:
متد applyPendingMigrations، کار وهله سازی IUnitOfWork را انجام میدهد. سپس متد Migrate آنرا اجرا میکند، تا تمام Migrations تولید شده، اما اعمال نشدهی به بانک اطلاعاتی، به صورت خودکار به آن اعمال شوند. متد Migrate نیز به صورت زیر تعریف میشود:
مرحلهی آخر: انتخاب بانک اطلاعاتی در برنامهی آغازین
پس از این تنظیمات، قسمتی که کار تعریف IUnitOfWork و همچنین DbContext جاری برنامه را انجام میدهد، به صورت زیر پیاده سازی میشود:
در اینجا ابتدا مقدار InUseKey از فایل تنظیمات برنامه دریافت میشود. بر اساس مقدار آن، رشتهی اتصالی مدنظر دریافت شده و سپس یکی از دو حالت SQLite و یا SQLServer انتخاب میشوند. برای مثال اگر Sqlite انتخاب شده باشد، IUnitOfWork به SQLiteDbContext تنظیم میشود. به این ترتیب لایهی سرویس برنامه که با IUnitOfWork کار میکند، به صورت خودکار وهلهای از SQLiteDbContext را دریافت خواهد کرد.
آزمایش برنامه
ابتدا کدهای کامل این مطلب را از اینجا دریافت کنید: EFCoreMultipleDb.zip
سپس آنرا اجرا نمائید. چنین تصویری را مشاهده خواهید کرد:
اکنون برنامه را بسته و سپس فایل appsettings.json را جهت تغییر مقدار InUseKey به کلید SqlServerConnection ویرایش کنید:
اینبار اگر مجددا برنامه را اجرا کنید، چنین خروجی قابل مشاهدهاست:
مقدار username، در contextهای هر کدام از این بانکهای اطلاعاتی، با مقدار متفاوتی به عنوان اطلاعات اولیهی آن ثبت شدهاست. سرویسی هم که اطلاعات آنرا تامین میکند، به صورت زیر تعریف شدهاست:
همانطور که مشاهده میکنید، با تغییر context برنامه، هیچ نیازی به تغییر کدهای UsersService نیست؛ چون اساسا این سرویس نمیداند که IUnitOfWork چگونه تامین میشود.
هر بانک اطلاعاتی باید Migration و Context خاص خودش را داشته باشد
تامین کنندهی بانکهای اطلاعاتی مختلف، عموما تنظیمات خاص خودشان را داشته و همچنین دستورات SQL متفاوتی را نیز تولید میکنند. به همین جهت نمیتوان از یک تک Context، هم برای SQLite و هم SQL Server استفاده کرد. به علاوه قصد داریم اطلاعات Migrations هر کدام را نیز در یک اسمبلی جداگانه قرار دهیم. در یک چنین حالتی EF نمیپذیرد که Context تولید کنندهی Migration، در اسمبلی دیگری قرار داشته باشد و باید حتما در همان اسمبلی Migration قرار گیرد. بنابراین ساختار پوشه بندی مثال جاری به صورت زیر خواهد بود:
- در پوشهی EFCoreMultipleDb.DataLayer فقط اینترفیس IUnitOfWork را قرار میدهیم. از این جهت که وقتی قرار شد در برنامه چندین Context تعریف شوند، لایهی سرویس برنامه قرار نیست بداند در حال حاضر با کدام Context کار میکند. به همین جهت است که تغییر بانک اطلاعاتی برنامه، تغییری را در کدهای اصلی آن ایجاد نخواهد کرد.
- در پوشهی EFCoreMultipleDb.DataLayer.SQLite کدهای Context و همچنین IDesignTimeDbContextFactory مخصوص SQLite را قرار میدهیم.
- در پوشهی EFCoreMultipleDb.DataLayer.SQLServer کدهای Context و همچنین IDesignTimeDbContextFactory مخصوص SQL Server را قرار میدهیم.
برای نمونه ابتدای Context مخصوص SQLite چنین شکلی را دارد:
public class SQLiteDbContext : DbContext, IUnitOfWork { public SQLiteDbContext(DbContextOptions options) : base(options) { } public virtual DbSet<User> Users { set; get; }
namespace EFCoreMultipleDb.DataLayer.SQLite.Context { public class SQLiteDbContextFactory : IDesignTimeDbContextFactory<SQLiteDbContext> { public SQLiteDbContext CreateDbContext(string[] args) { var basePath = Directory.GetCurrentDirectory(); Console.WriteLine($"Using `{basePath}` as the BasePath"); var configuration = new ConfigurationBuilder() .SetBasePath(basePath) .AddJsonFile("appsettings.json") .Build(); var builder = new DbContextOptionsBuilder<SQLiteDbContext>(); var connectionString = configuration.GetConnectionString("SqliteConnection") .Replace("|DataDirectory|", Path.Combine(basePath, "wwwroot", "app_data")); builder.UseSqlite(connectionString); return new SQLiteDbContext(builder.Options); } } }
تنظیمات رشتههای اتصالی بانکهای اطلاعاتی مختلف
در اینجا محتویات فایل appsettings.json را که در آن تنظیمات رشتههای اتصالی دو بانک SQL Server LocalDB و همچنین SQLite در آن ذکر شدهاند، مشاهده میکنید:
{ "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "SqlServerConnection": "Data Source=(LocalDB)\\MSSQLLocalDB;Initial Catalog=ASPNETCoreSqlDB;AttachDbFilename=|DataDirectory|\\ASPNETCoreSqlDB.mdf;Integrated Security=True;MultipleActiveResultSets=True;", "SqliteConnection": "Data Source=|DataDirectory|\\ASPNETCoreSqliteDB.sqlite", "InUseKey": "SqliteConnection" } }
یک کلید InUseKey را هم در اینجا تعریف کردهایم تا مشخص باشد در ابتدای کار برنامه، کلید کدام رشتهی اتصالی مورد استفاده قرار گیرد. برای مثال در اینجا کلید رشتهی اتصالی SQLite تنظیم شدهاست.
در این تنظیمات یک DataDirectory را نیز مشاهده میکنید. مقدار آن در فایل Startup.cs برنامه به صورت زیر بر اساس پوشهی جاری تعیین میشود و در نهایت به wwwroot\app_data اشاره خواهد کرد:
var connectionStringKey = Configuration.GetConnectionString("InUseKey"); var connectionString = Configuration.GetConnectionString(connectionStringKey) .Replace("|DataDirectory|", Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "app_data"));
دستورات تولید Migrations و به روز رسانی بانک اطلاعاتی
چون تعداد Contextهای برنامه بیش از یک مورد شدهاست، دستورات متداولی را که تاکنون برای تولید Migrations و یا به روز رسانی ساختار بانک اطلاعاتی اجرا میکردید، با پیام خطایی که این مساله را گوشزد میکند، متوقف خواهند شد. راه حل آن ذکر صریح Context مدنظر است:
برای تولید Migrations، از طریق خط فرمان، به پوشهی اسمبلی مدنظر وارد شده و دستور زیر را اجرا کنید:
For /f "tokens=2-4 delims=/ " %%a in ('date /t') do (set mydate=%%c_%%a_%%b) For /f "tokens=1-2 delims=/:" %%a in ("%TIME: =0%") do (set mytime=%%a%%b) dotnet build dotnet ef migrations --startup-project ../EFCoreMultipleDb.Web/ add V%mydate%_%mytime% --context SQLiteDbContext
دو سطر اول آن، زمان اجرای دستورات را به عنوان نام فایلها تولید میکنند.
پس از تولید Migrations، اکنون نوبت به تولید بانک اطلاعاتی و یا به روز رسانی بانک اطلاعاتی موجود است:
dotnet build dotnet ef --startup-project ../EFCoreMultipleDb.Web/ database update --context SQLServerDbContext
بدیهی است این رویه را پس از هربار تغییراتی در موجودیتهای برنامه و یا تنظیمات آنها در Contextهای متناظر، نیاز است مجددا اجرا کنید. البته اجرای اولین دستور اجباری است؛ اما میتوان دومین دستور را به صورت زیر نیز اجرا کرد:
namespace EFCoreMultipleDb.Web { public class Startup { public void Configure(IApplicationBuilder app, IHostingEnvironment env) { applyPendingMigrations(app); // ... } private static void applyPendingMigrations(IApplicationBuilder app) { var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>(); using (var scope = scopeFactory.CreateScope()) { var uow = scope.ServiceProvider.GetService<IUnitOfWork>(); uow.Migrate(); } } } }
namespace EFCoreMultipleDb.DataLayer.SQLite.Context { public class SQLiteDbContext : DbContext, IUnitOfWork { // ... public void Migrate() { this.Database.Migrate(); } } }
مرحلهی آخر: انتخاب بانک اطلاعاتی در برنامهی آغازین
پس از این تنظیمات، قسمتی که کار تعریف IUnitOfWork و همچنین DbContext جاری برنامه را انجام میدهد، به صورت زیر پیاده سازی میشود:
namespace EFCoreMultipleDb.Web { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddScoped<IUsersService, UsersService>(); var connectionStringKey = Configuration.GetConnectionString("InUseKey"); var connectionString = Configuration.GetConnectionString(connectionStringKey) .Replace("|DataDirectory|", Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "app_data")); switch (connectionStringKey) { case "SqlServerConnection": services.AddScoped<IUnitOfWork, SQLServerDbContext>(); services.AddDbContext<SQLServerDbContext>(options => { options.UseSqlServer( connectionString, dbOptions => { var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds; dbOptions.CommandTimeout(minutes); dbOptions.EnableRetryOnFailure(); }); }); break; case "SqliteConnection": services.AddScoped<IUnitOfWork, SQLiteDbContext>(); services.AddDbContext<SQLiteDbContext>(options => { options.UseSqlite( connectionString, dbOptions => { var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds; dbOptions.CommandTimeout(minutes); }); }); break; default: throw new NotImplementedException($"`{connectionStringKey}` is not defined in `appsettings.json` file."); } services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }
آزمایش برنامه
ابتدا کدهای کامل این مطلب را از اینجا دریافت کنید: EFCoreMultipleDb.zip
سپس آنرا اجرا نمائید. چنین تصویری را مشاهده خواهید کرد:
اکنون برنامه را بسته و سپس فایل appsettings.json را جهت تغییر مقدار InUseKey به کلید SqlServerConnection ویرایش کنید:
{ "ConnectionStrings": { // … "InUseKey": "SqlServerConnection" } }
مقدار username، در contextهای هر کدام از این بانکهای اطلاعاتی، با مقدار متفاوتی به عنوان اطلاعات اولیهی آن ثبت شدهاست. سرویسی هم که اطلاعات آنرا تامین میکند، به صورت زیر تعریف شدهاست:
namespace EFCoreMultipleDb.Services { public interface IUsersService { Task<User> FindUserAsync(int userId); } public class UsersService : IUsersService { private readonly IUnitOfWork _uow; private readonly DbSet<User> _users; public UsersService(IUnitOfWork uow) { _uow = uow; _users = _uow.Set<User>(); } public Task<User> FindUserAsync(int userId) { return _users.FindAsync(userId); } } }
پاسخ به بازخوردهای پروژهها
درخواست مستندات
دقیقا قصد همین است که کاربر جداول و فیلدها را ببیند و انتخاب کند، طبیعتا اکثر کاربران امکان نوشتن و فهمیدن دستورات SQL را ندارند، حال آنکه اگر کسی توانست که چه بهتر!
علت تولید فایل XML نیز این است که بتوان از آن در زبانهای مختلف استفاده کرد، مثلا در Applicationهای تحت وب php یا mvc یا هر زبان دیگری ولی در اینجا فعلا قصد ایجاد گزارش با زبان سی شارپ و کتابخانه pdfreport را داریم.
حالتی که نیز میفرمائید از یک سری متغیر در برنامه استفاده کنید نیز به ذهنم رسیده بود، که مثلا یه سری تنظیمات مربوط به گزارشات ایجاد کنیم تا کاربر بتواند قسمتهای مختلف گزارش را با توجه به این تنظیمات تغییر دهد. مثلا در فوتر گزارشها فلان متن نوشته شود، یا رنگ متون جداول فلان رنگ باشد.
ولی با استفاده از این روش میتوانید شکل و قیافه گزارش را تغییر دهید نه اینکه یک گزارش جدید تهیه کنید و به برنامه اضافه نمایید.
هدف اصلی ساختن طراح گزارشی تقریبا شبیه به stimulsoft میباشد، که در پس زمینه هنگامی که شما اجزای گزارش را به روی صفحه drag&drop میکنید، در واقع در حال تهیه فایل کد سی شارپ آن میباشد، حال اینجا برای اینکه حالت کلیتری باشد از ساختار xml استفاده میکنیم تا بعدا هر کسی(برنامه نویس ها) با توجه به نیاز خود providerهایی را برای تفسیر این فایل xml بنویسند.
نظرات مطالب
ساخت بستههای نیوگت مخصوص NET Core.
به روز رسانی
با حذف فایل project.json در VS 2017، اکنون با کلیک راست بر روی گروه نام پروژه (فایل csproj)، گزینهی Edit آن ظاهر شده و مداخل ذکر شدهی در مطلب فوق، چنین تعاریفی را پیدا میکنند:
برای تولید مستندات و کنترل اخطارهای آن:
و اگر خواستید دات نت 4.6 یا فریم ورکهای دیگر را نیز پشتیبانی کنید، ItemGroupهای آنها به این صورت اضافه میشوند:
با حذف فایل project.json در VS 2017، اکنون با کلیک راست بر روی گروه نام پروژه (فایل csproj)، گزینهی Edit آن ظاهر شده و مداخل ذکر شدهی در مطلب فوق، چنین تعاریفی را پیدا میکنند:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <Description>desc.</Description> <VersionPrefix>1.0.0</VersionPrefix> <Authors>name</Authors> <TargetFramework>netstandard1.6</TargetFramework> <GenerateDocumentationFile>true</GenerateDocumentationFile> <AssemblyName>name</AssemblyName> <PackageId>name</PackageId> <PackageTags>MVC;aspnetcore</PackageTags> <PackageProjectUrl>https://github.com/proj</PackageProjectUrl> <PackageLicenseUrl>https://github.com/proj/blob/master/LICENSE.md</PackageLicenseUrl> <PackageTargetFallback>$(PackageTargetFallback);dnxcore50</PackageTargetFallback> <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="1.1.2" /> </ItemGroup> <PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <PlatformTarget>anycpu</PlatformTarget> </PropertyGroup> <Target Name="PostcompileScript" AfterTargets="Build"> <Exec Command="dotnet pack --no-build --configuration $(Configuration)" /> </Target> </Project>
<PropertyGroup> <NoWarn>$(NoWarn);1591</NoWarn> <GenerateDocumentationFile>true</GenerateDocumentationFile> </PropertyGroup>
<PropertyGroup> <TargetFrameworks>net46;netstandard1.3</TargetFrameworks> </PropertyGroup> <ItemGroup Condition=" '$(TargetFramework)' == 'net46' "> <Reference Include="System" /> <Reference Include="System.Core" /> </ItemGroup> <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' "> <PackageReference Include="System.Data.Common" Version="4.3.0" /> </ItemGroup>
اگر مطلب تبدیل پلاگینهای جیکوئری به کنترلهای ASP.Net را مطالعه کرده باشید، در مورد ClientID بحث شد. با مراجعه به اصول HTML درخواهیم یافت که هر کنترل یا شیء مربوطه میتواند شامل ID و name باشد. عموما در کدهای جاوا اسکریپتی برای دسترسی به یک شیء در صفحه از ID آن شیء به صورت document.getElementById استفاده شده و از name برای پردازشهای سمت سرور استفاده میشود. برای مثال اگر به دوران ASP کلاسیک برگردیم، از شیء Request برای دریافت مقادیر ارسال شده به سرور با استفاده از name شیء مورد نظر استفاده میشد/میشود.
در حالت فرمهای معمولی ، ID و name عموما یکسان هستند. اما اگر از master page ها استفاده شود یا از یک user control برای ترکیب چندین کنترل کمک گرفته شود، دیگر ID و name در فرم رندر شده نهایی ASP.Net یکسان نخواهند بود. برای مثال:
<input name="ctl00$ContentPlaceHolder1$txtID" type="text" id="ctl00_ContentPlaceHolder1_txtID" />
public virtual string ClientID
{
get
{
this.EnsureID();
string uniqueID = this.UniqueID;
if ((uniqueID != null) && (uniqueID.IndexOf(this.IdSeparator) >= 0))
{
return uniqueID.Replace(this.IdSeparator, '_');
}
return uniqueID;
}
}
<Container>$<Container>$<Container>$<Container>$...$<ID>
این نکته در طراحی کنترلهای ASP.Net که از کدهای جاوا اسکریپتی استفاده میکنند بسیار مهم است. در تست اول و با یک صفحه ساده کنترل شما خوب کار خواهد کرد. اما اگر همین کنترل را بر روی یک صفحه مشتق شده از یک master page قرار دهید، دیگر ID آن یافت نشده و کدهای جاوا اسکریپتی شما کار نخواهند کرد. به همین جهت اگر قرار است قسمت جاوا اسکریپتی کنترل شما توسط کنترل به صورت خودکار ایجاد شود و در این کد ارجاعی به شیء جاری وجود دارد، این شیء جاری با استفاده از ClientID آن در سمت کلاینت قابل دسترسی خواهد بود و نه با استفاده از ID سمت سرور و یا UniqueID آن.