ASP.NET MVC #16
اگه از روش اول برای ثبت استثناها توسط Elmah استفاده شود، یعنی حذف یک سطر از فایل Global.asax.cs، فقط استثناها ثبت می شود ، یا صفحه خطایی هم نمایش داده می شود؟
تنظیمات ابتدایی Kendo UI Editor
در ذیل کدهای سمت کاربر فعال سازی مقدماتی Kendo UI را مشاهده میکنید. در قسمت tools آن، لیست امکانات و نوار ابزار مهیای آن درج شدهاند.
دو مورد insertImage و insertFile آن نیاز به تنظیمات سمت کاربر و سرور بیشتری دارند.
<!--نحوهی راست به چپ سازی --> <div class="k-rtl"> <textarea id="editor" rows="10" cols="30" style="height: 440px"></textarea> </div> @section JavaScript { <script type="text/javascript"> $(function () { $("#editor").kendoEditor({ tools: [ "bold", "italic", "underline", "strikethrough", "justifyLeft", "justifyCenter", "justifyRight", "justifyFull", "insertUnorderedList", "insertOrderedList", "indent", "outdent", "createLink", "unlink", "insertImage", "insertFile", "subscript", "superscript", "createTable", "addRowAbove", "addRowBelow", "addColumnLeft", "addColumnRight", "deleteRow", "deleteColumn", "viewHtml", "formatting", "cleanFormatting", "fontName", "fontSize", "foreColor", "backColor", "print" ], imageBrowser: { messages: { dropFilesHere: "فایلهای خود را به اینجا کشیده و رها کنید" }, transport: { read: { url: "@Url.Action("GetFilesList", "KendoEditorImages")", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET', cache: false }, destroy: { url: "@Url.Action("DestroyFile", "KendoEditorImages")", type: "POST" }, create: { url: "@Url.Action("CreateFolder", "KendoEditorImages")", type: "POST" }, thumbnailUrl: "@Url.Action("GetThumbnail", "KendoEditorImages")", uploadUrl: "@Url.Action("UploadFile", "KendoEditorImages")", imageUrl: "@Url.Action("GetFile", "KendoEditorImages")?path={0}" } }, fileBrowser: { messages: { dropFilesHere: "فایلهای خود را به اینجا کشیده و رها کنید" }, transport: { read: { url: "@Url.Action("GetFilesList", "KendoEditorFiles")", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET', cache: false }, destroy: { url: "@Url.Action("DestroyFile", "KendoEditorFiles")", type: "POST" }, create: { url: "@Url.Action("CreateFolder", "KendoEditorFiles")", type: "POST" }, uploadUrl: "@Url.Action("UploadFile", "KendoEditorFiles")", fileUrl: "@Url.Action("GetFile", "KendoEditorFiles")?path={0}" } } }); }); </script> }
منهای قسمت thumbnailUrl، عملکرد قسمتهای مختلف افزودن فایل و تصویر این ادیتور یکسان هستند. به همین جهت میتوان برای مثال کنترلی مانند KendoEditorFilesController را ایجاد و سپس در کنترلر KendoEditorImagesController از آن ارث بری کرد و متد دریافت و نمایش بند انگشتی تصاویر را افزود. به این ترتیب دیگر نیازی به تکرار کدهای مشترک بین این دو قسمت نخواهد بود.
نمایش لیست پوشهها و تصویر در ابتدای باز شدن صفحهی درج تصویر
با کلیک بر روی دکمهی نمایش لیست تصاویر، صفحه دیالوگی مانند شکل زیر ظاهر خواهد شد:
تنظیمات خواندن این فایلها، از قسمت read مربوط به imageBrowser دریافت میشود که cache آن نیز به false تنظیم شدهاست تا در این بین مرورگر اطلاعات را کش نکند. این مورد در حین حذف فایلها و پوشهها مهم است. زیرا اگر cache:false تنظیم نشده باشد، حذف یک فایل یا پوشه در سمت کاربر تاثیری نخواهد داشت.
imageBrowser: { transport: { read: { url: "@Url.Action("GetFilesList", "KendoEditorImages")", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET', cache: false } } },
namespace KendoUI13.Controllers { public class KendoEditorFilesController : Controller { //مسیر پوشه فایلها protected string FilesFolder = "~/files"; protected string KendoFileType = "f"; protected string KendoDirType = "d"; [HttpGet] public ActionResult GetFilesList(string path) { path = GetSafeDirPath(path); var imagesList = new DirectoryInfo(path) .GetFiles() .Select(fileInfo => new KendoFile { Name = fileInfo.Name, Size = fileInfo.Length, Type = KendoFileType }).ToList(); var foldersList = new DirectoryInfo(path) .GetDirectories() .Select(directoryInfo => new KendoFile { Name = directoryInfo.Name, Type = KendoDirType }).ToList(); return new ContentResult { Content = JsonConvert.SerializeObject(imagesList.Union(foldersList), new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }), ContentType = "application/json", ContentEncoding = Encoding.UTF8 }; } protected string GetSafeDirPath(string path) { // path = مسیر زیر پوشهی وارد شده if (string.IsNullOrWhiteSpace(path)) { return Server.MapPath(FilesFolder); } //تمیز سازی امنیتی path = Path.GetDirectoryName(path); path = Path.Combine(Server.MapPath(FilesFolder), path); return path; } } }
namespace KendoUI13.Models { public class KendoFile { public string Name { set; get; } public string Type { set; get; } public long Size { set; get; } } }
- علت استفاده از CamelCasePropertyNamesContractResolver در حین بازگشت JSON نهایی، تبدیل خواص دات نتی، به نامهای سازگار با JavaScript است. برای مثال به صورت خودکار Name را تبدیل به name میکند.
- پارامتر path در ابتدای کار خالی است. اما کاربر میتواند در بین پوشههای باز شدهی توسط مرورگر تصاویر Kendo UI حرکت کند. به همین جهت مقدار آن باید هربار بررسی شده و بر این اساس لیست فایلها و پوشههای جاری بازگشت داده شوند.
مدیریت حذف تصاویر و پوشهها
همانطور که در شکل فوق نیز مشخص است، با انتخاب یک پوشه یا فایل، دکمهای با آیکن ضربدر جهت فراهم آوردن امکان حذف، ظاهر میشود. این دکمه متصل است به قسمت destroy تنظیمات ادیتور:
imageBrowser: { transport: { destroy: { url: "@Url.Action("DestroyFile", "KendoEditorImages")", type: "POST" } } },
namespace KendoUI13.Controllers { public class KendoEditorFilesController : Controller { //مسیر پوشه فایلها protected string FilesFolder = "~/files"; protected string KendoFileType = "f"; protected string KendoDirType = "d"; [HttpPost] public ActionResult DestroyFile(string name, string path) { //تمیز سازی امنیتی name = Path.GetFileName(name); path = GetSafeDirPath(path); var pathToDelete = Path.Combine(path, name); var attr = System.IO.File.GetAttributes(pathToDelete); if ((attr & FileAttributes.Directory) == FileAttributes.Directory) { Directory.Delete(pathToDelete, recursive: true); } else { System.IO.File.Delete(pathToDelete); } return Json(new object[0]); } } }
- پارامتر name دریافتی مساوی است با نام فایل انتخاب شده و path مشخص میکند که در کدام پوشه قرار داریم.
- چون در اینجا امکان حذف یک پوشه یا فایل وجود دارد، حتما نیاز است بررسی کنیم، مسیر دریافتی پوشهاست یا فایل و سپس بر این اساس جهت حذف آنها اقدام صورت گیرد.
مدیریت ایجاد یک پوشهی جدید
تنظیمات قسمت create مرورگر تصاویر، مرتبط است به زمانیکه کاربر با کلیک بر روی دکمهی +، درخواست ایجاد یک پوشهی جدید را کردهاست:
imageBrowser: { transport: { create: { url: "@Url.Action("CreateFolder", "KendoEditorImages")", type: "POST" } } },
namespace KendoUI13.Controllers { public class KendoEditorFilesController : Controller { //مسیر پوشه فایلها protected string FilesFolder = "~/files"; protected string KendoFileType = "f"; protected string KendoDirType = "d"; [HttpPost] public ActionResult CreateFolder(string name, string path) { //تمیز سازی امنیتی name = Path.GetFileName(name); path = GetSafeDirPath(path); var dirToCreate = Path.Combine(path, name); Directory.CreateDirectory(dirToCreate); return KendoFile(new KendoFile { Name = name, Type = KendoDirType }); } protected ActionResult KendoFile(KendoFile file) { return new ContentResult { Content = JsonConvert.SerializeObject(file, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }), ContentType = "application/json", ContentEncoding = Encoding.UTF8 }; } } }
- پس از ایجاد پوشه، باید نام آنرا با فرمت KendoFile به صورت JSON بازگشت داد. همچنین در اینجا Type را نیز باید به d (پوشه) تنظیم کرد.
مدیریت قسمت ارسال فایل و تصویر
زمانیکه کاربر بر روی دکمهی upload file یا بارگذاری تصاویر در اینجا کلیک میکند، اطلاعات فایل آپلودی به مسیر uploadUrl ارسال میگردد.
imageBrowser: { transport: { thumbnailUrl: "@Url.Action("GetThumbnail", "KendoEditorImages")", uploadUrl: "@Url.Action("UploadFile", "KendoEditorImages")", imageUrl: "@Url.Action("GetFile", "KendoEditorImages")?path={0}" } },
در ادامه کدهای مدیریت سمت سرور قسمت آپلود این ادیتور را مشاهده میکنید:
namespace KendoUI13.Controllers { public class KendoEditorFilesController : Controller { //مسیر پوشه فایلها protected string FilesFolder = "~/files"; protected string KendoFileType = "f"; protected string KendoDirType = "d"; [HttpPost] public ActionResult UploadFile(HttpPostedFileBase file, string path) { //تمیز سازی امنیتی var name = Path.GetFileName(file.FileName); path = GetSafeDirPath(path); var pathToSave = Path.Combine(path, name); file.SaveAs(pathToSave); return KendoFile(new KendoFile { Name = name, Size = file.ContentLength, Type = KendoFileType }); } } }
- پس از ذخیره سازی اطلاعات فایل، نیاز است اطلاعات فایل نهایی را با فرمت KendoFile به صورت JSON بازگشت دهیم.
ارث بری از KendoEditorFilesController جهت تکمیل قسمت مدیریت تصاویر
تا اینجا کدهایی را که ملاحظه کردید، برای هر دو قسمت ارسال تصویر و فایل کاربرد دارند. قسمت ارسال تصاویر برای تکمیل نیاز به متد دریافت تصاویر به صورت بند انگشتی نیز دارد که به صورت ذیل قابل تعریف است و چون از کلاس پایه KendoEditorFilesController ارث بری کردهاست، این کنترلر به صورت خودکار حاوی اکشن متدهای کلاس پایه نیز خواهد بود.
using System.Web.Mvc; namespace KendoUI13.Controllers { public class KendoEditorImagesController : KendoEditorFilesController { public KendoEditorImagesController() { // بازنویسی مسیر پوشهی فایلها FilesFolder = "~/images"; } [HttpGet] [OutputCache(Duration = 3600, VaryByParam = "path")] public ActionResult GetThumbnail(string path) { //todo: create thumb/ resize image path = GetSafeFileAndDirPath(path); return File(path, "image/png"); } } }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.
اگر اکشن متدی تنها دارای یک پارامتر باشد:
public Task<IActionResult> RefreshToken(string refreshToken)
{ "refreshToken": "test" }
- یک روش دیگر حل این مشکل، استفاده از JToken به عنوان نوع پارامتر است (و حالتیکه اطلاعات با فرمت JSON ارسال میشوند):
public Task<IActionResult> RefreshToken([FromBody]JToken jsonBody) { var refreshToken = jsonBody.Value<string>("refreshToken");
- روش دیگر آن، تعریف پارامتر از نوع یک کلاس است که در این حالت از مزایای اعتبارسنجی به همراه ویژگیها نیز میتوان استفاده کرد:
public class EntityData { public string RefreshToken { get; set; } }
EF Code First #11
- بله. اسامی مهم نیست. یکی عنوان میکند BLL یکی Infrastructure یکی Service یکی...
- خروجی لایه سرویس فقط باید از نوع اشیاء معمولی، لیست یا IEnumerable باشد. اگر از IQueryable به عنوان خروجی متد استفاده کردید، به یک Abstraction دارای «نشتی» خواهید رسید. چون به سادگی خارج از منطقی که مدنظر بوده کاملا قابل تغییر است.
- God Class کلاسی است که اطلاعات زیادی را در اختیار سایرین قرار میدهد، نه کلاسی که جهت برآورده سازی منطق تجاری برنامه، از اطلاعات زیادی استفاده میکند. کلاسی که 1000 تا متد را در اختیار سایر کلاسها قرار میدهد God class نام دارد. نه متدی که برای عملکرد صحیح خود نیاز به اطلاعات زیادی است.
اگر به جستجوی گوگل دقت کرده باشید، به صورت Ajax ایی پیاده سازی شدهاست، با این تفاوت که بعد از هر تغییر درجستجوی مورد نظر، Url صفحه نیز تغییر میکند (برای مثال بعد از جستجوی عبارت dotNetTips آدرس بار صفحه به شکل https://www.google.com/#q=dotNetTips&* تغییر میکند). برای پیاده سازی این ویژگی باید از تکنیکی به نام HashChange استفاده کرد. در نتیجه با این روش مشکل ارسال صفحهای خاص در یک گرید برای دیگران، به صورت Ajax ایی و بدون مشکل انجام میشود. از این رو با توجه به داشتن Urlهای منحصر به فرد برای هر صفحه، تا حدی مشکل سئو سایت را نیز برطرف میکنیم.
برای استفاده از این ویژگی در ادامه قصد داریم پیاده سازی کتابخانهی MvcAjaxPager را مورد بررسی قرار دهیم. ابتدا قبل از هر کاری، با استفاده از دستور زیر اقدام به نصب کتابخانه آن مینماییم:
Install-Package MvcAjaxPager
در ادامه نحوه پیاده سازی آن را به همراه مثالی، مورد بررسی قرار میدهیم:
ابتدا یک مدل فرضی را همانند زیر تهیه میکنیم :
public class Topic { public int Id; public string Title; public string Text; }
public class TopicService { public static IEnumerable<Topic> Topics = new List<Topic>() { new Topic{Id=1,Title="Title 1",Text= "Text 1"}, new Topic{Id=2,Title="Title 2",Text="Text 2"}, new Topic{Id=3,Title="Title 3",Text="Text 3"}, new Topic{Id=4,Title="Title 4",Text="Text 4"}, new Topic{Id=5,Title="Title 5",Text="Text 5"}, new Topic{Id=6,Title="Title 6",Text="Text 6"}, new Topic{Id=7,Title="Title 7",Text="Text 7"}, new Topic{Id=8,Title="Title 8",Text="Text 8"}, new Topic{Id=9,Title="Title 9",Text="Text 9"}, new Topic{Id=10,Title="Title 10",Text="Text 10"}, new Topic{Id=11,Title="Title 11",Text="Text 11"}, new Topic{Id=12,Title="Title 12",Text="Text 12"}, new Topic{Id=13,Title="Title 13",Text="Text 13"}, new Topic{Id=14,Title="Title 14",Text="Text 14"}, new Topic{Id=15,Title="Title 15",Text="Text 15"}, new Topic{Id=16,Title="Title 16",Text="Text 16"}, new Topic{Id=17,Title="Title 17",Text="Text 17"}, new Topic{Id=18,Title="Title 18",Text="Text 18"}, new Topic{Id=19,Title="Title 19",Text="Text 19"}, new Topic{Id=20,Title="Title 20",Text="Text 20"}, new Topic{Id=21,Title="Title 21",Text="Text 21"}, new Topic{Id=22,Title="Title 22",Text="Text 22"}, }; public static IEnumerable<Topic> GetAll() { return Topics.OrderBy(row => row.Id); } }
public class ListViewModel { public IEnumerable<Topic> Topics { get; set; } public int PageIndex { get; set; } public int TotalItemCount { get; set; } }
public ActionResult Index(int page = 1) { var topics = TopicService.GetAll (); int totalItemCount = topics.Count(); var model = new ListViewModel() { PageIndex = page, Topics = topics.OrderBy(p => p.Id).Skip((page - 1) * 10).Take(10).ToList(), TotalItemCount = totalItemCount }; if (!Request.IsAjaxRequest()) { return View(model); } return PartialView("_TopicList", model); }
و در Partial view مربوطه نیز داریم :
@using MvcAjaxPager @model ListViewModel @Html.AjaxPager(Model.TotalItemCount, 10, Model.PageIndex, "Index", "Home", null, new PagerOptions { ShowDisabledPagerItems = true, AlwaysShowFirstLastPageNumber = true, HorizontalAlign = "center", ShowFirstLast = false, CssClass = "NavigationBox", AjaxUpdateTargetId = "dvTopics", AjaxOnBegin = "AjaxStart", AjaxOnComplete = "AjaxStop" }, null, null) <table> <tr> <th> @Html.DisplayName("ID") </th> <th> @Html.DisplayName("Title") </th> <th> @Html.DisplayName("Text") </th> </tr> @foreach (var topic in Model.Topics) { <tr> <td> @topic.Id </td> <td> @topic.Title </td> <td> @topic.Text </td> </tr> } </table> @Html.AjaxPager(Model.TotalItemCount, 10, Model.PageIndex, "Index", "Home", null, new PagerOptions { ShowDisabledPagerItems = true, AlwaysShowFirstLastPageNumber = true, HorizontalAlign = "center", ShowFirstLast = true, FirstPageText = "اولین", LastPageText = "آخرین", MorePageText = "...", PrevPageText = "قبلی", NextPageText = "بعدی", CssClass = "NavigationBox", AjaxUpdateTargetId = "dvTopics", AjaxOnBegin = "AjaxStart", AjaxOnComplete = "AjaxStop" }, null, null)
حال برای استفاده از pager مورد نظر فقط کافیست متد AjaxPager آن را فراخوانی کنیم. این متد شامل 11 OverLoad مختلف هست.
در این قسمت TotalItemCount جمع کل رکوردها، PageSize تعداد رکوردهای هر صفحه و PageIndex آدرس صفحه جاری میباشد.
مهمترین بخش این pager که قابلیتهای زیادی را به کاربر میدهد، قسمت PagerOptions آن است و تعدادی از پارامترهای آن شامل AjaxOnBeginAjaxOnCompelte، AjaxOnSuccess ، AjaxOnFailure میتوان تعیین کرد تا بعد از شروع، وقوع خطا، موفقیت و یا خاتمه عملیات جاوا اسکریپتی، اجرا شود.
AlwaysShowFirstLastPageNumber جهت نمایش صفحه اول و آخر
FirstPageText جهت تعیین متن اولین صفحه
LastPageText جهت تعیین متن آخرین صفحه
CssClass ، Id جهت تعیین Id خاص
و در انتها، در view مربوطه داریم:
@using MvcAjaxPager @model ListViewModel @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Index</title> </head> <body> <div id="dvTopics"> @{ @Html.Partial("_TopicList", Model); } </div> <script type="text/javascript" src="@Url.Content("~/Scripts/jquery-1.7.2.min.js")"></script> <script type="text/javascript" src="@Url.Content("~/Scripts/path.min.js")"></script> <script type="text/javascript" src="@Url.Content("~/Scripts/jquery.pager-1.0.1.min.js")"></script> <script type="text/javascript"> $('.NavigationBox').pager(); //pagination before start function AjaxStart() { console.log('Start AJAX call. Loading message can be shown'); } // pagination - after request function AjaxStop() { console.log('Stop AJAX call. Loading message can be hidden'); }; </script> </body> </html>
این روزها توسعه دهندگان مرورگرها، شمارههای نگارشهای برنامههای خود را مرتبا و با اعداد نجومی بالا میبرند. امروز فایرفاکس 5، تا چند وقت دیگر فایرفاکس 8 هم در راه خواهد بود؛ سایر مرورگرها نیز به همین ترتیب. به همراه نگارشهای متفاوت دات نت، یک سری browser definition files نیز وجود دارد که تنها بازهی محدودی از این شماره نگارشها در آنها تعریف شده است. در نتیجه زمانیکه مثلا مرورگر کاربر IE 10 باشد، آنرا به عنوان unknown browser تشخیص داده و یک سری از قابلیتها را به صورت خودکار حذف میکند. برای مثال جاوا اسکریپت برای این نوع مرورگرها خاموش میشود. منظور از خاموش کردن جاوا اسکریپت این است که موتور ASP.NET Webforms دیگر یک سری اسکریپتهای تولیدی مخصوص خود را به صفحه تزریق نخواهد کرد؛ برای مثال تمام PostBackهای سایت شما از کار خواهند افتاد و مسایلی از این دست.
روش حل آن هم ساده است. باید فایلهای firefox.browser و ie.browser به روز شده را در پوشهی استاندارد App_Browsers کپی کنید و همین!
به زودی یک آپدیت دات نت نیز جهت به روز رسانی در سطح ماشین، ارائه خواهد شد. فعلا مجبور هستید به ازای تک تک برنامههای خود این پوشهی به روز شده را کپی کنید.
جهت توضیحات بیشتر و دریافت فایلهای ذکر شده به این مطلب مراجعه نمائید.
برش تصاویر قبل از آپلود (Crop)
<div style="overflow: auto"> <img id="preview" /> </div>
در این بین من با استفاده از فریمورک بوت استراپ کلاس img-responsive را به تگ image میدهم تا تصویر در هر صفحه نمایشی متناسب با اندازه آن نمایش داده شود. مشکل زیبایی تصویر در صفحه و نحوه کراپ کردن آن حل میشود ولی مشکل جدیدتر این است که تصویری که کراپ میشود آن ناحیه ای نیست که شما قبلا انتخاب کرده بودید؛ دلیل آن هم این است که تصویری که responsive شده است اندازه جدیدی برای خود دارد و برش ناحیه در سمت کلاینت و مختصاتی که به شما داده میشود بر اساس اندازه تغییر یافته است ولی در سمت سرور شما با اندازه واقعی تصویر سر و کار دارید و به همین دلیل مختصات ناحیه برش داده شده اشتباه بوده و قسمتهای دیگری از تصویر برش میخورند.
برای حل این مشکل ابتدا دو المان مخفی زیر را به صفحه اضافه میکنیم:
<input type="hidden" id="RealW" name="RealW" /> <input type="hidden" id="RealH" name="RealH" />
سپس کدهای زیر را به فایل js به شکل زیر ویرایش میکنیم:
$('#preview').Jcrop({ aspectRatio: 2, bgFade: true, bgOpacity: .3, onChange: updateInfo, onSelect: updateInfo }, function () { // use the Jcrop API to get the real image size //============== خطوط جدید $("#RealW").val($('#preview').css("width").replace("px", "")); $("#RealH").val($('#preview').css("height").replace("px", "")); //============== jcrop_api = this; });
سپس در سمت کلاینت با محاسبه فرمولهای تناسب این مشکل را رفع میکنیم تا مختصاتهای دریافت شده را به مختصاتهای واقعی تبدیل کنیم:
public static byte[] Resize(this byte[] byteImageIn, int x1,int y1,int x2,int y2,int realW,int realH) { //convert to full size image ImageConverter ic = new ImageConverter(); Image src = (Image)(ic.ConvertFrom(byteImageIn)); //original size if (src == null) return null; // چهار خط زیر فرمول تناسب را اجرا میکند x1 = src.Width * x1 / realW; x2 = src.Width * x2 / realW; y1 = src.Height * y1 / realH; y2 = src.Height * y2 / realH; Bitmap target = new Bitmap(x2 - x1, y2 - y1); using (Graphics graphics = Graphics.FromImage(target)) graphics.DrawImage(src, new Rectangle(0, 0, target.Width, target.Height), new Rectangle(x1,y1,x2-x1,y2-y1), GraphicsUnit.Pixel); src = target; using (var ms = new MemoryStream()) { src.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); return ms.ToArray(); } }
در این مقاله با یکی از مهمترین ویژگیهای 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 است:
در ورژنهای قبلی ویژوال استودیو، در زمان بارگذاری پروژه، احتیاجی به اجرای نرم افزارهای تحریم گذر نبود؛ همانند ورژن 15.6. ولی در این ورژن که من نصب کردم بدلیل نصب خودکار کتابخانههای متریال دیزاین، باید از این گونه نرم افزارها نیز استفاده کرد.
درقسمت بعدی گزینه BlankApp را انتخاب و در قسمت Minimum Android Version که با انتخاب آن میتوانیم ورژن گوشیهای اندروید برای استفاده از این اپلیکیشن را انتخاب نماییم. به عنوان مثال با انتخاب اندروید 4.4 برنامه ما صرفا برای گوشیهای اندورید 4.4 به بالا جواب میدهد. بعد از تایید، پروژه باز شده که با این solution روبرو میشویم.
- قسمت Properties را اگر بازکنیم، با دو گزینه روبرو میشویم که یکی فایل android manifest هست و اگر روی properties آن کلیک و ویژگیهایی را انتخاب کنیم، بطور خودکار بر روی manifest تاثیر میگذارند. در قسمتهای بعد در این رابطه جداگانه بحث خواهیم کرد.
- در قسمت Asset که به معنای منابع اندروید میباشد، به عنوان مثال صفحات Razor، فونت و یا صفحات HTML و یا عکس و یا ... را میتوانیم قرار دهیم.
- در قسمت Resource که پوشههای آن layout ،mipmap ،values و resource.designer میباشند، در پوشه layout میتوانیم صفحات استاندارد اندروید را شروع به طراحی کنیم. درقسمت mipmap عکسها و یا فایلهای xml ایی را که قرار است استفاده کنیم، در پروژه قرار میدهیم. در قسمت value که بیشتر برای انتخاب و تغییر تم یا استفاده از Resourceها (همانند Asp.mvc که استفاده میکردیم) است که البته با ساختاری متفاوت در اندروید از آنها استفاده میکنیم، قرار میگیرند.
- در قسمت Resource.Designer که در مطالب بعد با آن آشنا خواهید شد، تمامی آیتمهای انتخابی از جمله Layout ها و عکسها و... با ذخیره کردن در این قسمت دخیره میشوند که بعد با رفرنس دادن از طریق resource پروژه میتوانیم از عکسها و لیآوتها در کد نویسی استفاده کنیم.
- در انتها جهت معرفی به mainactivity میرسیم که یک صفحه است شامل المنتها و اجزای مختلف و کاربر میتواند با آن ارتباط برقرار کند.
[Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)] public class MainActivity : AppCompatActivity { protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); // Set our view from the "main" layout resource SetContentView(Resource.Layout.activity_main); } }
- در گزینه بعدی Mainluncher را میبینیم که تعیین کنندهی نقطه شروع اکتیویتی ما در بین اکتیویتیهای دیگر میباشد.
بدیهی است درایورهای مربوطه به گوشی اندروید را باید تهیه کرد که در سایت مربوط به سازنده و یا در سایتهای دیگر میتوانید دانلود کنید. اولین برنامه را مینویسیم که هدف از آن، اجرای 10 دکمه بصورت داینامیک هست و اینکه با کلیک بر روی هر کدام از دکمهها، رنگ آن آبی شود.
protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); LinearLayout ln; Button btn; // Set our view from the "main" layout resource SetContentView(Resource.Layout.activity_main); for (int i = 0; i < 5; i++) { btn = new Button(this); btn.Text = i.ToString(); ln= FindViewById<LinearLayout>(Resource.Id.linearLayout1); btn.Click += Btn_Click; ln.AddView(btn); } } private void Btn_Click(object sender, System.EventArgs e) { Button btntest = sender as Button; btntest.SetBackgroundColor(Android.Graphics.Color.Blue); } }
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:minWidth="25px" android:minHeight="25px"> <LinearLayout android:orientation="vertical" android:minWidth="25px" android:minHeight="25px" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/linearLayout1" /> </RelativeLayout>