لیست تازههای IIS 7.5
در این مقاله با یکی از مهمترین ویژگیهای git یعنی بازیابی تغییرات فایلها، آشنا میشویم. اما در ابتدا نگاهی میکنیم به چگونگی ایجاد تغییر در آخرین commit:
تغییر آخرین commit:
در
git این امکان وجود دارد که آخرین فرمان commit با استفاده از اصلاحکننده
amend تغییر کند. علت تاکید بر روی آخرین دستور این است که git به دلیل
ساختاری که دارد نمیتواند commitهای قبل را تغییر دهد. اگر مقالات ابتدایی
آموزش git را مطالعه کرده باشید، به خاطر دارید که هر commit دارای یک کد
منحصر به فرد SHA-1 است، که این کد از هش کردن BLOBها به همراه خود مقادیر
commit یعنی مشخصات ایجاد کننده آن و از همه مهمتر SHA-1 پدر ایجاد میشود.
در نتیجه تغییر commitیی که نقش برگ را ندارد، یعنی در ساختار درختی git
دارای فرزند است، سبب میشود کد SHA-1 آن تغییر کند. این تغییر، commitهای فرزند را مجاب میکند برای حفظ صحت دادهها مقدار SHA-1 خود را تغییر دهند.
به این ترتیب این تغییرات در کل repository پخش خواهد شد. به همین دلیل git
جز آخرین commit امکان اصلاح دیگر commitها را نخواهد داد.
برای اصلاح آخرین commit کافی است دستور commit خود را با amend-- بیاورید
دستورات بازیابی فایل:
دستور checkout:
این فرمان یکی از مهمترین فرمانهای git است که دارای دو کاربرد است:
۱) بازیابی فایلی از repository و یا stage
۲) تغییر شاخه (این مورد را در مقالات مربوط به branch بررسی خواهیم کرد)
با
استفاده از این دستور میتوان فایلی را از repository به stage یا working
tree و یا هر دو بیاوریم. عملکرد این دستور با اصلاح کنندههای گوناگون
متفاوت خواهد بود. در ادامه روشهای مختلف فراخوانی این دستور و کاربرد هر
کدام آورده شده است:
در صورتی که بخواهیم فایلی را از محلی که head اکنون به آن اشاره میکند به working tree بیاوریم از دستور زیر استفاده میکنیم:
git checkout --[filename]
در حالت فوق فایل مستقیما به working tree آورده شده و در stage قرار نمیگیرد
تذکر:
-- در دستور بالا اختیاری بوده، اما استفاده از آن توصیه میشود. زیرا در
صورتیکه نام فایل به اشتباه وارد شود و یا فایل موجود نباشد، git اقدام به
تعویض شاخه میکند. زیرا همانطور که گفته شد، این دستور کاربرد دوگانه دارد.
در این حالت ممکن است به علت سهل انگاری مشکلاتی ایجاد شود علامت -- تاکید میکند که مقدار نوشته نام فایل است.
حال اگر بخواهیم فایلی را از commitهای قبل بازیابی کنیم، میتوانیم از دستور زیر استفاده کنیم:
git checkout [SHA-1] [filename]
در این حالت فایل هم در stage و هم در working tree قرار میگیرد.
دستور reset:
در
صورتیکه بخواهید تعداد زیادی فایل را به وضعیت مشخصی در زمان قبل
برگدانید، reset فرمان مناسبی خواهد بود. البته استفاده از این دستور باید با
احتیاط کامل صورت گیرد. زیرا در صورت اشتباه، این امکان وجود دارد که دیگر
نتوانید به بخشی از سوابق فایلهای خود دسترسی داشته باشید. بنابراین این
دستور همانقدر که کاربردی است، به همان اندازه نیز خطرناک است.
دستور reset را میتوان به ۳ صورت اجرا نمود:
۱) soft
۲) mixed (حالت پیشفرض)
۳) hard
۱)در
حالت soft تنها head به commit گفته شده منتقل میشود و working tree و
همچنین stage تغییری نمیکند. دقیقا مانند آنکه هد یک نوار خوان ویدئویی به
جای آنکه به آخرین محل ضبط اشاره کند، به عقب برگشته و به قسمتی در قبل
برود. در این حالت در صورتیکه دستور commit جدیدی ایجاد نشود که باعث پاک
شدن commitهای از آنجا به بعد شود، میتوان با اجرای مجدد دستور reset و اشاره
به آخرین commit، مجددا head را به سر جای اول برگرداند. البته توجه کنید در
صورتیکه در هنگام برگرداندن head به commitهای قبلی، فایلهایی تغییر کرده
باشند، آنها به صورت خودکار به stage اضافه میشوند.
۲) در حالت mixed
که پیش فرض این دستور نیز است، working tree بدون تغییر میماند. اما stage
تغییر کرده و دقیقا مانند وضعیت commit میشود.
۳) در این حالت هم
working tree و هم stage تغییر میکند و عینا وضعیت commitیی را میگیرند
که اکنون head به آن اشاره میکند. استفاده از این اصلاح کننده بسیار
خطرناکتر از موارد قبل است.
در هر یک از موارد فوق تا زمانیکه دستور
commit جدیدی را اجرا نکرده باشید، میتوانید به وضعیت قبل برگردید. اما اگر
commit جدید اجرا شود دیگر امکان بازگشت به commitهای صورت گرفته بعد از
آن وجود ندارد.
نکته مهم:
علیرغم
آنکه میتوان به commitهای گذشته در صورت عدم داشتن commit جدید مراجعه
کرد، اما یک اشکال فنی وجود دارد و آن این است که شما نمیتوانید SHA-1های آن commitها را با دستوراتی نظیر log ببینید. بنابراین بهتر است مقدار آنها
را قبل از اجرای دستور، ذخیره و تا اطمینان از وضعیت فعلی در محلی نگه دارید.
شکل زیر نمایانگر وضعیتهای مختلف دستور reset در هنگام بازگشت به سه commit قبل نسبت به وضعیت فعلی Head است:
در قسمت قبلی بروز رسانی موجودیتهای منفصل با WCF را بررسی کردیم. در این قسمت خواهیم دید چگونه میتوان تغییرات موجودیتها را تشخیص داد و عملیات CRUD را روی یک Object Graph اجرا کرد.
تشخیص تغییرات با Web API
فرض کنید میخواهیم از سرویسهای Web API برای انجام عملیات CRUD استفاده کنیم، اما بدون آنکه برای هر موجودیت متدهایی مجزا تعریف کنیم. به بیان دیگر میخواهیم عملیات مذکور را روی یک Object Graph انجام دهیم. مدیریت دادهها هم با مدل Code-First پیاده سازی میشود. در مثال جاری یک اپلیکیشن کنسول خواهیم داشت که بعنوان یک کلاینت سرویس را فراخوانی میکند. هر پروژه نیز در Solution مجزایی قرار دارد، تا یک محیط n-Tier را شبیه سازی کنیم.
مدل زیر را در نظر بگیرید.
همانطور که میبینید مدل ما آژانسهای مسافرتی و رزرواسیون آنها را ارائه میکند. میخواهیم مدل و کد دسترسی دادهها را در یک سرویس Web API پیاده سازی کنیم تا هر کلاینتی که به HTTP دسترسی دارد بتواند عملیات CRUD را انجام دهد. برای ساختن سرویس مورد نظر مراحل زیر را دنبال کنید:
- در ویژوال استودیو پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب پروژه را Web API انتخاب کنید. نام پروژه را به Recipe3.Service تغییر دهید.
- کنترلر جدیدی بنام TravelAgentController به پروژه اضافه کنید.
- دو کلاس جدید با نامهای TravelAgent و Booking بسازید و کد آنها را مطابق لیست زیر تغییر دهید.
public class TravelAgent { public TravelAgent() { this.Bookings = new HashSet<Booking>(); } public int AgentId { get; set; } public string Name { get; set; } public virtual ICollection<Booking> Bookings { get; set; } } public class Booking { public int BookingId { get; set; } public int AgentId { get; set; } public string Customer { get; set; } public DateTime BookingDate { get; set; } public bool Paid { get; set; } public virtual TravelAgent TravelAgent { get; set; } }
- با استفاده از NuGet Package Manager کتابخانه Entity Framework 6 را به پروژه اضافه کنید.
- کلاس جدیدی بنام Recipe3Context بسازید و کد آن را مطابق لیست زیر تغییر دهید.
public class Recipe3Context : DbContext { public Recipe3Context() : base("Recipe3ConnectionString") { } public DbSet<TravelAgent> TravelAgents { get; set; } public DbSet<Booking> Bookings { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<TravelAgent>().HasKey(x => x.AgentId); modelBuilder.Entity<TravelAgent>().ToTable("TravelAgents"); modelBuilder.Entity<Booking>().ToTable("Bookings"); } }
- فایل Web.config پروژه را باز کنید و رشته اتصال زیر را به قسمت ConnectionStrings اضافه کنید.
<connectionStrings> <add name="Recipe3ConnectionString" connectionString="Data Source=.; Initial Catalog=EFRecipes; Integrated Security=True; MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings>
- فایل Global.asax را باز کنید و کد زیر را به متد Application_Start اضافه نمایید. این کد بررسی Model Compatibility در EF را غیرفعال میکند. همچنین به JSON serializer میگوییم که self-referencing loop خاصیتهای پیمایشی را نادیده بگیرد. این حلقه بدلیل ارتباط bidirectional بین موجودیتها بوجود میآید.
protected void Application_Start() { // Disable Entity Framework Model Compatibilty Database.SetInitializer<Recipe1Context>(null); // The bidirectional navigation properties between related entities // create a self-referencing loop that breaks Web API's effort to // serialize the objects as JSON. By default, Json.NET is configured // to error when a reference loop is detected. To resolve problem, // simply configure JSON serializer to ignore self-referencing loops. GlobalConfiguration.Configuration.Formatters.JsonFormatter .SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; ... }
- فایل RouteConfig.cs را باز کنید و قوانین مسیریابی را مانند لیست زیر تغییر دهید.
public static void Register(HttpConfiguration config) { config.Routes.MapHttpRoute( name: "ActionMethodSave", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional }); }
- در آخر کنترلر TravelAgent را باز کنید و کد آن را مطابق لیست زیر بروز رسانی کنید.
public class TravelAgentController : ApiController { // GET api/travelagent [HttpGet] public IEnumerable<TravelAgent> Retrieve() { using (var context = new Recipe3Context()) { return context.TravelAgents.Include(x => x.Bookings).ToList(); } } /// <summary> /// Update changes to TravelAgent, implementing Action-Based Routing in Web API /// </summary> public HttpResponseMessage Update(TravelAgent travelAgent) { using (var context = new Recipe3Context()) { var newParentEntity = true; // adding the object graph makes the context aware of entire // object graph (parent and child entities) and assigns a state // of added to each entity. context.TravelAgents.Add(travelAgent); if (travelAgent.AgentId > 0) { // as the Id property has a value greater than 0, we assume // that travel agent already exists and set entity state to // be updated. context.Entry(travelAgent).State = EntityState.Modified; newParentEntity = false; } // iterate through child entities, assigning correct state. foreach (var booking in travelAgent.Bookings) { if (booking.BookingId > 0) // assume booking already exists if ID is greater than zero. // set entity to be updated. context.Entry(booking).State = EntityState.Modified; } context.SaveChanges(); HttpResponseMessage response; // set Http Status code based on operation type response = Request.CreateResponse(newParentEntity ? HttpStatusCode.Created : HttpStatusCode.OK, travelAgent); return response; } } [HttpDelete] public HttpResponseMessage Cleanup() { using (var context = new Recipe3Context()) { context.Database.ExecuteSqlCommand("delete from [bookings]"); context.Database.ExecuteSqlCommand("delete from [travelagents]"); } return Request.CreateResponse(HttpStatusCode.OK); } }
- در ویژوال استودیو پروژه جدیدی از نوع Console application بسازید و نام آن را به Recipe3.Client تغییر دهید.
- فایل program.cs را باز کنید و کد آن را مطابق لیست زیر بروز رسانی کنید.
internal class Program { private HttpClient _client; private TravelAgent _agent1, _agent2; private Booking _booking1, _booking2, _booking3; private HttpResponseMessage _response; private static void Main() { Task t = Run(); t.Wait(); Console.WriteLine("\nPress <enter> to continue..."); Console.ReadLine(); } private static async Task Run() { var program = new Program(); program.ServiceSetup(); // do not proceed until clean-up is completed await program.CleanupAsync(); program.CreateFirstAgent(); // do not proceed until agent is created await program.AddAgentAsync(); program.CreateSecondAgent(); // do not proceed until agent is created await program.AddSecondAgentAsync(); program.ModifyAgent(); // do not proceed until agent is updated await program.UpdateAgentAsync(); // do not proceed until agents are fetched await program.FetchAgentsAsync(); } private void ServiceSetup() { // set up infrastructure for Web API call _client = new HttpClient {BaseAddress = new Uri("http://localhost:6687/")}; // add Accept Header to request Web API content negotiation to return resource in JSON format _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); } private async Task CleanupAsync() { // call cleanup method in service _response = await _client.DeleteAsync("api/travelagent/cleanup/"); } private void CreateFirstAgent() { // create new Travel Agent and booking _agent1 = new TravelAgent {Name = "John Tate"}; _booking1 = new Booking { Customer = "Karen Stevens", Paid = false, BookingDate = DateTime.Parse("2/2/2010") }; _booking2 = new Booking { Customer = "Dolly Parton", Paid = true, BookingDate = DateTime.Parse("3/10/2010") }; _agent1.Bookings.Add(_booking1); _agent1.Bookings.Add(_booking2); } private async Task AddAgentAsync() { // call generic update method in Web API service to add agent and bookings _response = await _client.PostAsync("api/travelagent/update/", _agent1, new JsonMediaTypeFormatter()); if (_response.IsSuccessStatusCode) { // capture newly created travel agent from service, which will include // database-generated Ids for each entity _agent1 = await _response.Content.ReadAsAsync<TravelAgent>(); _booking1 = _agent1.Bookings.FirstOrDefault(x => x.Customer == "Karen Stevens"); _booking2 = _agent1.Bookings.FirstOrDefault(x => x.Customer == "Dolly Parton"); Console.WriteLine("Successfully created Travel Agent {0} and {1} Booking(s)", _agent1.Name, _agent1.Bookings.Count); } else Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase); } private void CreateSecondAgent() { // add new agent and booking _agent2 = new TravelAgent {Name = "Perry Como"}; _booking3 = new Booking { Customer = "Loretta Lynn", Paid = true, BookingDate = DateTime.Parse("3/15/2010")}; _agent2.Bookings.Add(_booking3); } private async Task AddSecondAgentAsync() { // call generic update method in Web API service to add agent and booking _response = await _client.PostAsync("api/travelagent/update/", _agent2, new JsonMediaTypeFormatter()); if (_response.IsSuccessStatusCode) { // capture newly created travel agent from service _agent2 = await _response.Content.ReadAsAsync<TravelAgent>(); _booking3 = _agent2.Bookings.FirstOrDefault(x => x.Customer == "Loretta Lynn"); Console.WriteLine("Successfully created Travel Agent {0} and {1} Booking(s)", _agent2.Name, _agent2.Bookings.Count); } else Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase); } private void ModifyAgent() { // modify agent 2 by changing agent name and assigning booking 1 to him from agent 1 _agent2.Name = "Perry Como, Jr."; _agent2.Bookings.Add(_booking1); } private async Task UpdateAgentAsync() { // call generic update method in Web API service to update agent 2 _response = await _client.PostAsync("api/travelagent/update/", _agent2, new JsonMediaTypeFormatter()); if (_response.IsSuccessStatusCode) { // capture newly created travel agent from service, which will include Ids _agent1 = _response.Content.ReadAsAsync<TravelAgent>().Result; Console.WriteLine("Successfully updated Travel Agent {0} and {1} Booking(s)", _agent1.Name, _agent1.Bookings.Count); } else Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase); } private async Task FetchAgentsAsync() { // call Get method on service to fetch all Travel Agents and Bookings _response = _client.GetAsync("api/travelagent/retrieve").Result; if (_response.IsSuccessStatusCode) { // capture newly created travel agent from service, which will include Ids var agents = await _response.Content.ReadAsAsync<IEnumerable<TravelAgent>>(); foreach (var agent in agents) { Console.WriteLine("Travel Agent {0} has {1} Booking(s)", agent.Name, agent.Bookings.Count()); } } else Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase); } }
- در آخر کلاسهای TravelAgent و Booking را به پروژه کلاینت اضافه کنید. اینگونه کدها بهتر است در لایه مجزایی قرار گیرند و بین پروژهها به اشتراک گذاشته شوند.
اگر اپلیکیشن کنسول (کلاینت) را اجرا کنید با خروجی زیر مواجه خواهید شد.
(Successfully created Travel Agent John Tate and 2 Booking(s
(Successfully created Travel Agent Perry Como and 1 Booking(s
(Successfully updated Travel Agent Perry Como, Jr. and 2 Booking(s
(Travel Agent John Tate has 1 Booking(s
(Travel Agent Perry Como, Jr. has 2 Booking(s
شرح مثال جاری
با اجرای اپلیکیشن Web API شروع کنید. این اپلیکیشن یک کنترلر MVC Web Controller دارد که پس از اجرا شما را به صفحه خانه هدایت میکند. در این مرحله سایت در حال اجرا است و سرویسها قابل دسترسی هستند.
سپس اپلیکیشن کنسول را باز کنید، روی خط اول کد فایل program.cs یک breakpoint قرار دهید و آن را اجرا کنید. ابتدا آدرس سرویس Web API را نگاشت میکنیم و با تنظیم مقدار خاصیت Accept Header از سرویس درخواست میکنیم که اطلاعات را با فرمت JSON بازگرداند.
بعد از آن با استفاده از آبجکت HttpClient متد DeleteAsync را فراخوانی میکنیم که روی کنترلر TravelAgent تعریف شده است. این متد تمام دادههای پیشین را حذف میکند.
در قدم بعدی سه آبجکت جدید میسازیم: یک آژانس مسافرتی و دو رزرواسیون. سپس این آبجکتها را با فراخوانی متد PostAsync روی آبجکت HttpClient به سرویس ارسال میکنیم. اگر به متد Update در کنترلر TravelAgent یک breakpoint اضافه کنید، خواهید دید که این متد آبجکت آژانس مسافرتی را بعنوان یک پارامتر دریافت میکند و آن را به موجودیت TravelAgents در Context جاری اضافه مینماید. این کار آبجکت آژانس مسافرتی و تمام آبجکتهای فرزند آن را در حالت Added اضافه میکند و باعث میشود که context جاری شروع به ردیابی (tracking) آنها کند.
نکته: قابل ذکر است که اگر موجودیتهای متعددی با مقداری یکسان در خاصیت کلید اصلی (Primary-key value) دارید باید مجموعه آبجکتهای خود را Add کنید و نه Attach. در مثال جاری چند آبجکت Booking داریم که مقدار کلید اصلی آنها صفر است (Bookings with Id = 0). اگر از Attach استفاده کنید EF پیغام خطایی صادر میکند چرا که چند موجودیت با مقادیر کلید اصلی یکسان به context جاری اضافه کرده اید.
بعد از آن بر اساس مقدار خاصیت Id مشخص میکنیم که موجودیتها باید بروز رسانی شوند یا خیر. اگر مقدار این فیلد بزرگتر از صفر باشد، فرض بر این است که این موجودیت در دیتابیس وجود دارد بنابراین خاصیت EntityState را به Modified تغییر میدهیم. علاوه بر این فیلدی هم با نام newParentEntity تعریف کرده ایم که توسط آن بتوانیم کد وضعیت مناسبی به کلاینت بازگردانیم. در صورتی که مقدار فیلد Id در موجودیت TravelAgent برابر با یک باشد، مقدار خاصیت EntityState را به همان Added رها میکنیم.
سپس تمام آبجکتهای فرزند آژانس مسافرتی (رزرواسیون ها) را بررسی میکنیم و همین منطق را روی آنها اعمال میکنیم. یعنی در صورتی که مقدار فیلد Id آنها بزرگتر از 0 باشد وضعیت EntityState را به Modified تغییر میدهیم. در نهایت متد SaveChanges را فراخوانی میکنیم. در این مرحله برای موجودیتهای جدید اسکریپتهای Insert و برای موجودیتهای تغییر کرده اسکریپتهای Update تولید میشود. سپس کد وضعیت مناسب را به کلاینت بر میگردانیم. برای موجودیتهای اضافه شده کد وضعیت 201 (Created) و برای موجودیتهای بروز رسانی شده کد وضعیت 200 (OK) باز میگردد. کد 201 به کلاینت اطلاع میدهد که رکورد جدید با موفقیت ثبت شده است، و کد 200 از بروز رسانی موفقیت آمیز خبر میدهد. هنگام تولید سرویسهای REST-based بهتر است همیشه کد وضعیت مناسبی تولید کنید.
پس از این مراحل، آژانس مسافرتی و رزرواسیون جدیدی میسازیم و آنها را به سرویس ارسال میکنیم. سپس نام آژانس مسافرتی دوم را تغییر میدهیم، و یکی از رزرواسیونها را از آژانس اولی به آژانس دومی منتقل میکنیم. اینبار هنگام فراخوانی متد Update تمام موجودیتها شناسه ای بزرگتر از 1 دارند، بنابراین وضعیت EntityState آنها را به Modified تغییر میدهیم تا هنگام ثبت تغییرات دستورات بروز رسانی مناسب تولید و اجرا شوند.
در آخر کلاینت ما متد Retreive را روی سرویس فراخوانی میکند. این فراخوانی با کمک متد GetAsync انجام میشود که روی آبجکت HttpClient تعریف شده است. فراخوانی این متد تمام آژانسهای مسافرتی بهمراه رزرواسیونهای متناظرشان را دریافت میکند. در اینجا با استفاده از متد Include تمام رکوردهای فرزند را بهمراه تمام خاصیت هایشان (properties) بارگذاری میکنیم.
دقت کنید که مرتب کننده JSON تمام خواص عمومی (public properties) را باز میگرداند، حتی اگر در کد خود تعداد مشخصی از آنها را انتخاب کرده باشید.
نکته دیگر آنکه در مثال جاری از قراردادهای توکار Web API برای نگاشت درخواستهای HTTP به اکشن متدها استفاده نکرده ایم. مثلا بصورت پیش فرض درخواستهای POST به متدهایی نگاشت میشوند که نام آنها با "Post" شروع میشود. در مثال جاری قواعد مسیریابی را تغییر داده ایم و رویکرد مسیریابی RPC-based را در پیش گرفته ایم. در اپلیکیشنهای واقعی بهتر است از قواعد پیش فرض استفاده کنید چرا که هدف Web API ارائه سرویسهای REST-based است. بنابراین بعنوان یک قاعده کلی بهتر است متدهای سرویس شما به درخواستهای متناظر HTTP نگاشت شوند. و در آخر آنکه بهتر است لایه مجزایی برای میزبانی کدهای دسترسی داده ایجاد کنید و آنها را از سرویس Web API تفکیک نمایید.
سازماندهی برنامههای Angular توسط ماژولها
برای طراحی گزارش شما میتوانید به سه روش این کار را انجام دهید.
1- طراحی در برنامه طراح گزارش
2- طراحی از داخل ویژوال استودیو
3- طراحی گزارش در زمان اجرا
برای شروع شما میتوانید نسخه آزمایشی این گزارشساز را دریافت کنید. تنها محدودیت این نسخه نمایش عبارت Demo در چاپ میباشد.
برنامه Designer را اجرا کنید. در صورتی که برای اولین بار است این برنامه را اجرا میکنید ابتدا باید رابط کاربری خود را انتخاب نمایید. نوار ابزار سمت چپ تمامی ابزارهای پرکاربرد طراحی گزارش را در اختیارتان قرار میدهد. ابزارهایی که در این بخش درباره آنها توضیح داده خواهد شد عبارتند از:
Header, Footer, Data, Page Header, Page Footer, Report Title, Report Summery
*به ابزارهای بالا Band گفته میشود.
Header , Footer :
همانطور که از نامشان پیداست در قسمت بالا و پایین بخشی از گزارش قرار میگیرند که برای استفاده در بالا و پایین بند Data میباشد. به عنوان مثال بند Header مناسب طراحی سرستونهای یک جدول میباشد و بند Footer هم جهت نمایش اطلاعات انتهایی یک جدول. ولی شما میتوانید با تنظیم خصوصیات هر بند رفتار و نمایش آنها را به طور کل تغییر دهید. نکته مثبت این گزارشساز این است که شما میتوانید بیش از یک واحد از هر بند را بر روی صفحه طراح خود قرار دهید، به عنوان مثال شما میتوانید دو بند Header داشته باشید که یکی در صفحات زوج و دیگری در صفحات فرد نمایش داده شود.
Data :
این بند جهت نمایش اطلاعات از منبع دادهها میباشد. به این معنا که به ازای هر سطر از دادهها یک بار این بخش نمایش داده میشود. تعداد دفعات نمایش این بند محدود به تعداد سطرهای منبع داده و یا اندازه صفحه و همچنین خصوصیت محدوده نمایش سطرها در یک صفحه میباشد.
Page Header , Page Footer :
این دو بند با توجه به نامشان جهت نمایش در بالا و پایین هر صفحه از گزارش میباشد. البته باز هم یادآور میشوم که با تغییر در خصوصیاتشان میتوانید رفتار و نحوه نمایش آنها را تغییر دهید.
Report Title :
این بند فقط در ابتدای گزارش نمایش داده خواهد شد.
Report Summery :
این بند بلافاصله بعد از اتمام گزارش نمایش داده خواهد شد.
مثال :
برای شروع در Designer یک گزارش جدید از نوع Blank Report ایجاد نمایید. سپس در پنل Dictionary بر روی New Item کلیک کرده و گزینه XML Data را انتخاب نمایید. با توجه به محل نصب گزارشساز وارد مسیر …\Bin\Data شده و فایلهای Demo.xsd و Demo.xml را برای قسمتهای مربوطه انتخاب نمایید. یک بار دیگر بر رو New Item کلیک کرده و گزینه New Data Source را انتخاب نمایید، از لیست ظاهر شده کانکشنی را که ایجاد کردهاید را انتخاب نمایید؛ نتیجه کار تا اینجا باید به صورت زبر باشد.
جدول Product را دراگ کرده و بر روی صفحه طراحی گزارش رها کنید. فرم Data ظاهر میشود این فرم را مطابق تصویر زیر تنظیم نمایید.
حال بر روی صفحه طراحی گزارش بندهای Header, Data, Footer مشاهده میشود؛ حال شما میتوانید با کلیک بر روی سربرگ Preview خروجی گزارش را ببینید.
توابع :
این گزارشساز دارای توابع بسیاری است که اکثر نیازهای شما را برطرف میکند به عنوان مثال تابع تبدیل عدد به حروف به زبان فارسی. همچنین شما میتوانید توابع خاص خود را ساخته و به صورت رفرنس به گزارش اضافه نمایید.
در این بخش ما از توابع موجود در گزارش استفاده خواهیم کرد. برای شروع بر روی کامپوننت Text در بند Footer زیر ستون UnitPrice دابل کلیک کرده تا فرم TextEditor ظاهر شود. سربرگ Summery را انتخاب نمایید. مطابق اطلاعات زیر بخشها را تنظیم نمایید.
Summery Function: Sum
Data Band: DataProducts
Data Column: Products.UnitPrice
حال بر روی سربرگ Preview
کلیک نمایید تا خروجی گزارش را ببینید. جمع ستون UnitPrice
فقط در صفحه آخر نمایش داده خواهد شد. اگر بخواهید جمع ستون در پایین هر صفحه
نمایش داده شود ابتدا باید خصوصیت Print on All Pages بند Footer به True
ست شود. سپس بر روی کامپوننت Text در بند Footer،
دابل کلیک نمایید و در فرم TextEditor
سربرگ Summery
تیک Running Total
را به حالت انتخاب شده در بیاورید، حال خروجی گزارش را ببینید، جمع در انتهای هر
صفحه ظاهر میشود.
متغیرها :
در این گزارش ساز دو نوع متغیر وجود دارد؛ نرمال و سیستمی. نوع سیستمی شامل متغیرهایی میشود که کاربرد مشخصی در تهیه گزارش دارند، مثل شماره صفحه، شماره ردیف، عنوان گزارش و ...
برای مثال شما میتوانید متغیر سیستمی Line را برای روی صفحه طراحی دراگ کنید. دو کامپوننت Text بر روی صفحه ایجاد میشود. اولی با محتوای Line و دومی با محتوای {Line}. اولی را به بند Header و دومی را به بند Data منتقل کنید و سپس خروجی گزارش را مشاهده نمایید، حال گزارش شما دارای شماره ردیف است.
متغیرهای نرمال تقریبا همانند متغیرهایی هستند که همه روزه شما در برنامههای خود از آنها استفاده میکنید. با کلیک بر روی New Item گزینه New Variable را انتخاب نمایید و نوع متغیر را Decimal انتخاب نمایید، سپس متغیر ایجاد شده را دراگ کرده و بروی صفحه طراحی قرار دهید و مشابه متغیر Line عمل کرده و کامپوننتهای Text را در بندهای مناسب قرار دهید. سپس بند Data بر روی صفحه طراحی را انتخاب نمایید، در پنل Properties بر روی Eventes کلیک کرده سپس در رویداد Rendering کد زیر را وارد نمایید.
Variable1 += Products.UnitPrice
حال در خروجی گزارش میتوانید مقادیر محاسبه
شده را ببینید. توجه داشته باشید که شما میتوانید در رویدادهای این گزارشساز به
زبان VB
و C#
برنامه نویسی کنید و محدود به یک خط کد نمیباشید.
شما میتوانید گزارش ساخته شده را به صورتهای مختلف ذخیره کنید از جمله کد C# و یا یک اپلیکیشن قابل اجرا.
8.ORM Lazy Load
List<Customer> customers = context.Customers.ToList(); foreach (Customer cust in context.Customers){ Console.WriteLine("Customer {0}, Account {1}", cust.Person.LastName.Trim() + ", " + cust.Person.FirstName, cust.AccountNumber); }
9.استفاده از MiniProfiler
#if DEBUG then // فعال سازی MiniProfiler #endif
10. Data Paging در بانک اطلاعاتی
11. بررسی تعداد کوئریهای صادر شده در یک صفحه و تعداد رکوردهای بازگشت داده شده توسط آنها
نحوه فارسی سازی فیلتر گرید کندو Kendo grid filter
سوالی که بنده درگیر اون شدم اینه که در پروژه فیلدی هست که تاریخ رو به صورت میلادی ذخیره میکنه . با مواردی که گفته شده نمایش تاریخ به صورت شمسی انجام شده اما وقتی که فیلتر روی این فیلد فعال میشه در گرید کندو در قسمت باکس فیلتر یک کامپوننت DatePicker میلادی ظاهر میشه .
متاسفانه تمامی راه هایی که میدونستم رو تست کردم اما به نتیجه خاصی نرسیدم .
1. تلاش شد که UI فیلتر به صورت رشته تعریف بشه توسط دستورهای Format. و UI. اما موارد ارائه شده برای این قسمت کارساز نبود .
2. تلاش شد که با استفاده از یک property جانبی از نوع رشته و تبدیل فیلد تاریخ در متدهای set و get این مسئله از راه دیگه حل بشه که نمایش اطلاعات موفقیت امیز بود اما در هنگام فیلتر متاسفانه به مشکلات تبدیل برخورد کردیم که قابل اصلاح نبود .
لطف بفرمایید راهنمایی کنید برای فیلتر تاریخ چه راهکاری وجود داره
همچنین اگر دقت کنید مثالی هم که در این صفحه هست DatePicker فیلتر ، تاریخ رو به صورت 7 اسفند 2016 نشون میده .
به صورت خلاصه:
- اگر فقط از کامپوننتهای کلاسی استفاده میکنید، mobx-react@5 برای کار شما پاسخگو است.
- اگر از کامپوننتهای کلاسی و همچنین کامپوننتهای تابعی در برنامهی خود استفاده میکنید، mobx-react@6 به همراه mobx-react-lite نیز ارائه میشود و هر دو روش را با هم پوشش میدهد.
- اگر فقط از کامپوننتهای تابعی جدید استفاده میکنید، هوکهای کتابخانهی کوچک mobx-react-lite برای کار شما کافی است.
معرفی useLocalStore Hook و useObserver Hook
در مطالب قبلی، روش تعریف یک کلاس مخزن حالت MobX را توسط تزئین کنندههایی مانند observable، computed و action بررسی کردیم. همچنین دریافتیم که تعریف یک چنین تزئین کنندههایی، یا نیاز به استفادهی از تایپاسکریپت را دارد و یا باید پروژهی React را جهت تغییر کامپایلر Babel آن و فعالسازی decorators، مقداری ویرایش کرد. با useLocalStore Hook هرچند تمام روشهای قبلی هنوز هم پشتیبانی میشوند، اما دیگر نیاز به استفادهی از decorators نیست. useLocalStore تابعی است که یک شیء را باز میگرداند. هر خاصیتی از این شیء، به صورت خودکار observable درنظر گرفته میشود. تمام getters آن به عنوان computed properties تفسیر میشوند و تمام متدهای آن، action درنظر گرفته خواهند شد.
یک مثال:
import React from 'react' import { useLocalStore, useObserver } from 'mobx-react' // 6.x export const SmartTodo = () => { const todo = useLocalStore(() => ({ title: 'Click to toggle', done: false, toggle() { todo.done = !todo.done }, get emoji() { return todo.done ? '😜' : '🏃' }, })) return useObserver(() => ( <h3 onClick={todo.toggle}> {todo.title} {todo.emoji} </h3> )) }
- روش استفادهی از تابع useLocalStore، میتواند به صورت محلی (همانند اسم آن) مختص به یک کامپوننت باشد. یعنی میتوان بجای state استاندارد React که اجازهی تغییر مستقیم خواص آنرا نمیدهد، از MobX State محلی ارائه شدهی توسط useLocalStore استفاده کرد و یا میتوان useLocalStore را به صورت global نیز تعریف کرد که در ادامهی بحث به آن میپردازیم.
- در مثال فوق، طول عمر شیء ایجاد شدهی توسط useLocalStore، محلی و محدود به طول عمر کامپوننت تابعی تعریف شدهاست.
- در اینجا شیء بازگشت داده شدهی توسط useLocalStore، دارای دو خاصیت title و done است. این دو خاصیت بدون نیاز به هیچ تعریف خاصی، observable در نظر گرفته میشوند. Fi به علاوه خاصیت getter آن به نام emoji نیز به عنوان یک خاصیت محاسباتی MobX تفسیر شده و متد toggle آن به صورت یک action پردازش میشود. بنابراین در حین کار با MobX Hooks دیگر نیازی به تغییر ساختار پروژهی React، برای پشتیبانی از decorators نیست.
- در این مثال، return useObserver را نیز مشاهده میکنید. کار آن رندر مجدد کامپوننت، با تغییر یکی از خواص observable ردیابی شدهی توسط آن است.
امکان تعریف global state با کمک useLocalStore
نام useLocalStore از این جهت انتخاب شدهاست که مشخص کند مخزن حالت ایجاد شدهی توسط آن، درون یک کامپوننت به صورت محلی ایجاد میشود و سراسری نیست. اما این نکته به این معنا نیست که نمیتوان مخزن حالت ایجاد شدهی توسط آنرا در بین سلسه مراتب کامپوننتهای برنامه به اشتراک گذاشت. توسط تابع useLocalStore میتوان چندین مخزن حالت را ایجاد کرد و سپس توسط شیءای دیگر آنها را یکی کرده و در آخر به کمک Context API خود React آنرا در اختیار تمام کامپوننتهای برنامه قرار داد.
تا نگارش MobX 5x (و همچنین پس از آن)، توسط inject@ میتوان یک مخزن حالت را در اختیار یک کامپوننت قرار داد (مانند inject('myStore')). طراحی inject@ مربوط است به زمانیکه امکان دسترسی به Context پشت صحنهی React به صورت عمومی توسط Context API آن ارائه نشده بود. به همین جهت از این پس دیگر نیازی به استفادهی از آن نیست.
چگونه توسط MobX Hooks، یک مخزن حالت سراسری را ایجاد کنیم؟
برای ایجاد یک مخزن حالت سراسری با روش جدید MobX Hooks، مراحل زیر را میتوان طی کرد:
الف) ایجاد شیء store
ابتدا متدی را مانند createStore ایجاد میکنیم، به نحوی که یک شیء را بازگشت دهد. این شیء همانطور که عنوان شد، خواصش، getters و متدهای آن، توسط MobX ردیابی خواهند شد (مانند const todo = useLocalStore مثال فوق) و نیازی به اعمال MobX Decorators را ندارند.
export function createStore() { return { // ... } }
ب) برپایی Context
اینبار دیگر نه از شیء Provider خود MobX استفاده میکنیم و نه از تزئین کنندهی inject@ آن؛ بلکه از React Context استاندارد استفاده خواهیم کرد:
import React from 'react'; import { createStore } from './createStore'; import { useLocalStore } from 'mobx-react'; // 6.x or mobx-react-lite@1.4.0 const storeContext = React.createContext(null); export const StoreProvider = ({ children }) => { const store = useLocalStore(createStore); return <storeContext.Provider value={store}>{children}</storeContext.Provider>; } export const useStore = () => { const store = React.useContext(storeContext); if (!store) { throw new Error('useStore must be used within a StoreProvider.'); } return store }
- سپس توسط React.createContext، یک شیء Context استاندارد React را ایجاد میکنیم؛ به نام storeContext.
- تابع کمکی StoreProvider، جایگزین شیء Provider قبلی MobX میشود. یعنی کارش محصور کردن کامپوننت App برنامه است تا شیء store را در اختیار سلسه مراتب کامپوننتهای React قرار دهد. در اینجا children به همان کامپوننتهایی که قرار است توسط Context.Provider محصور شوند اشاره میکند.
- تابع کمکی useStore، جهت محصور کردن متد React.useContext، اضافه شدهاست. میتوانید useContext Hook را به صورت مستقیم در کامپوننتهای تابعی فراخوانی کنید و یا میتوانید از متد کمکی useStore بجای آن استفاده نمائید تا حجم کدهای تکراری برنامه کاهش یابد.
ج) استفادهی از StoreProvider تهیه شده
اکنون با استفاده از متد StoreProvider فوق که شیء Context.Provider استاندارد React را بازگشت میدهد، میتوان کامپوننتهای بالاترین کامپوننت سلسه مراتب کامپوننتهای برنامه را محصور کرد، تا تمام آنها بتوانند به store ذخیره شدهی در Provider، دسترسی پیدا کنند:
export default function App() { return ( <StoreProvider> <main> <Component1 /> <Component2 /> <Component3 /> </main> </StoreProvider> ); }
د) استفاده از store مهیا شده در کامپوننتهای تابعی برنامه
پس از تهیهی متدی کمکی useStore که در حقیقت همان useContext Hook است، میتوان به کمک آن در کامپوننتهای تابعی، به store و تمام امکانات آن دسترسی پیدا کرد:
const store = useStore();
سؤال: آیا هنوز هم میتوان یک مخزن پیچیدهی متشکل از چندین کلاس را تشکیل داد؟
پاسخ: بله. برای مثال ابتدا دو کلاس جدید CounterStore و ThemeStore را به نحو متداولی، با استفادهی از MobX decorators طراحی میکنیم (دقیقا مانند مثال قسمت قبل). سپس بجای ذکر نال، بجای پارامتر متد createContext، آنرا با یک شیء جدید مقدار دهی میکنیم که هر کدام از خواص آن، به یک وهله از مخازن حالت ایجاد شده اشاره میکند:
export const storesContext = React.createContext({ counterStore: new CounterStore(), themeStore: new ThemeStore(), }); export const useStores = () => React.useContext(storesContext);
const { counterStore } = useStores();
چند نکتهی تکمیلی
نکته 1: با اشیاء MobX از Object Destructuring استفاده نکنید!
اگر بر روی اشیاء MobX از Object Destructuring استفاده کنیم، خروجی آن تبدیل به متغیرهای سادهای خواهند شد که دیگر ردیابی نمیشوند.
برای مثال اگر counterStore مثال فوق به همراه خاصیت observable ای به نام activeUserName است، آنرا به صورت زیر تبدیل به متغیر activeUserName نکنید؛ چون دیگر reactive نخواهد بود:
const { counterStore: { activeUserName }, } = useStores();
const { counterStore } = useStores();
نکته 2: مدیریت side effects با MobX
در مورد اثرات جانبی و side effects در مطلب «قسمت 32 - React Hooks - بخش 3 - نکات ویژهی برقراری ارتباط با سرور» بیشتر بحث شد. اگر یک اثر جانبی مانند تنظیم document.title، به مقدار یک خاصیت observable وابسته بود، میتوان از متد autorun که تغییرات آنها را ردیابی میکند، درون useEffect Hook استاندارد، استفاده کرد:
import React from 'react' import { autorun } from 'mobx' function useDocumentTitle(store) { React.useEffect( () => autorun(() => { document.title = `${store.title} - ${store.sectionName}` }), [], // note empty dependencies ) }
نکته 4: روش فعالسازی MobX strict mode
اگر strict mode را در Mobx به روش زیر فعال کنیم:
import { configure } from "mobx"; configure({ enforceActions: true });
نکته 3: روش انجام اعمال async در MobX
فرض کنید یک عملیات async را در یک اکشن متد کلاس حالت MobX، به صورت زیر انجام دادهایم و نتیجهی آن به خاصیت weatherData آن کلاس که observable است، به صورت مستقیم انتساب داده شدهاست:
@action loadWeather = city => { fetch( `https://abnormal-weather-api.herokuapp.com/cities/search?city=${city}` ) .then(response => response.json()) .then(data => { this.weatherData = data; }); };
راه حل اول: تغییر خاصیت this.weatherData را به یک اکشن متد مجزا انتقال میدهیم:
@action setWeather = data => { this.weatherData = data; };
راه حل دوم: اگر نمیخواهیم یک اکشن متد جدید را تعریف کنیم، میتوان از متد کمکی runInAction در داخل یک callback استفاده کرد:
loadWeatherInline = city => { fetch(`http://jsonplaceholder.typicode.com/comments/${city}`) .then(response => response.json()) .then(data => { runInAction(() => (this.weatherData = data)); }); };
در مورد اعمال async/await چطور؟
در اینجا هم تفاوتی نمیکند. هر چیزی پس از await، شبیه به حالت متد then پردازش میشود. به همین جهت در اینجا نیز باید از یکی از دو راه حل ارائه شده، استفاده کرد:
loadWeatherAsync = async city => { const response = await fetch( `http://jsonplaceholder.typicode.com/comments/${city}` ); const data = await response.json(); runInAction(() => { this.weatherData = data; }); };