مدیریت Bad Urls در ASP.NET MVC
استفاده از چند فرم در کنار هم در ASP.NET MVC
خیر از Httpget و NonAction استفاده نکردم ولی از ChildActionOnly بالای اکشنها استفاده کردم
در کنترلر به صورت زیر اکشنها رو نوشتم:
[Authorize(Roles = "Administrator")] public ActionResult AddUserRole() { return View(); } ////////////////////////////////////////////////////////// //اضافه کردن نقش//////////// [Authorize(Roles = "Administrator")] [HttpGet] [ChildActionOnly] public ActionResult AddRole() { return PartialView("PartialRole"); } [ChildActionOnly] [HttpPost] //[ValidateAntiForgeryToken] public ActionResult AddRole(tblRole rl, string submitValue) { if (submitValue == "ذخیره نقش") { //تبدیل تاریخ میلادی به شمسی rl.RoleDateCreate = Utility.ToPersianDate(DateTime.Now); UserRepository user = new UserRepository(); //برای وارد کردن خودکار شناسه کاربر وارد شده rl.UserIDCreate_FK = Convert.ToInt64(user.FindUserID(User.Identity.Name)); if (ModelState.IsValid) { RoleRepository acr = new RoleRepository(); var model = acr.Add(rl); ViewBag.Message = "نقش با موفقیت ثبت شد"; ModelState.Clear(); } } else { ModelState.Clear(); } return PartialView("PartialRole", rl); } ////////////////////////////////////////////////////////// //اضافه کردن کاربر////////// [Authorize(Roles = "Administrator")] [HttpGet] [ChildActionOnly] public ActionResult AddUser() { return PartialView("PartialUser"); } [ChildActionOnly] [HttpPost] public ActionResult AddUser(tblUser rl, string submitValue) { if (submitValue == "ثبت نام") { if (ur.ExistUserName(rl.UserName))//برای اینکه نام کاربری تکراری نباشد { ViewBag.Warning = "این نام کاربری تکراری میباشد"; } else { //برای وارد کردن خودکار شناسه کاربر وارد شده rl.UserIDCreate = Convert.ToInt64(ur.FindUserID(User.Identity.Name)); //تبدیل میلادی به شمسی rl.UserDateCreate = Utility.ToPersianDate(DateTime.Now); if (ModelState.IsValid) { UserRepository acr = new UserRepository(); var model = acr.Add(rl); ViewBag.Message = "کاربر با موفقیت ثبت شد"; ModelState.Clear(); } } } else { ModelState.Clear(); } return PartialView("PartialUser", rl); } ////////////////////////////////////////////////////////// // دادن نقش به کاربر/////// [ChildActionOnly] [HttpGet] [Authorize(Roles = "Administrator")] public ActionResult RoleOfUser() { var blUser = new UserRepository(); var blRole = new RoleRepository(); var model = new RoleUserViewModel(); model.username = blUser.Select().ToList(); model.rolename = blRole.Select().ToList(); return PartialView("PartialRoleOfUser", model); } [ChildActionOnly] [HttpPost] public ActionResult RoleOfUser(tblRoleUser rl, string submitValue) { if (submitValue == "ذخیره نقش کاربر") { //برای وارد کردن خودکار شناسه کاربر وارد شده rl.UserIDCreate_FK = Convert.ToInt64(ur.FindUserID(User.Identity.Name)); //تبدیل میلادی به شمسی rl.RoleUserDateCreate = Utility.ToPersianDate(DateTime.Now); if (ModelState.IsValid) { RoleUserRepository acr = new RoleUserRepository(); var model = acr.Add(rl); ViewBag.Message = "نقش کاربر با موفقیت ثبت شد"; ModelState.Clear(); } } else { ModelState.Clear(); } return PartialView("PartialRoleOfUser", rl); }
@model MeterControl.ViewModels.RoleUserViewModel <script src="~/Scripts/jquery-1.8.2.min.js"></script> <script src="~/Scripts/jquery.validate.min.js"></script> <script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script> @using (Ajax.BeginForm("AddUserRole", "Home", new AjaxOptions { HttpMethod = "Post", Url = "/Home/AddUserRole" })){ <fieldset> <legend>tblRole</legend> <div> @Html.LabelFor(model => model.role.RoleName) </div> <div> @Html.EditorFor(model => model.role.RoleName) @Html.ValidationMessageFor(model => model.role.RoleName) </div> @ViewBag.Message <p> <input type="submit" value="ذخیره نقش" name="submitValue" /> </p> </fieldset> }
و در ویو کلی هم به صورت زیر اکشنها صدا زده شده اند:
@{ ViewBag.Title = "AddUserRole"; } <h2>AddUserRole</h2> <div> <div> @Html.Action("AddRole", "Home") </div> <div> @Html.Action("AddUser", "Home") @Html.Partial("PartialUser") </div> <div> @Html.Action("RoleOfUser","Home") </div> </div>
به خط زیر خطا میدهد
@Html.Action("AddRole", "Home")
در خیلی مواقع ملاحظه میشود که برای نمایش تعدادی از رکوردهای یک جدول در پایگاه داده، کل مقادیر موجود درآن توسط یک دستور select به دست میآید و صفحهبندی خروجی، به کنترلهای موجود سپرده میشود. اگر پایگاه داده ما دارای تعداد زیادی رکورد باشد، آن موقع است که دچار مشکل میشویم. فرض کنید به طور همزمان ۵ نفر (که تعداد زیادی نیستند) از برنامه ما که شامل ۱۰۰۰۰۰ سطر داده میباشد استفاده کنند و در هر صفحه، ۱۰ رکورد نمایش داده شود و صفحهبندی ما از نوع معقولی نباشد. در این صورت به جای اینکه با ۵×۱۰ رکورد داده را بارگزاری کنیم، ۵×۱۰۰۰۰۰ رکورد یعنی ۵۰۰۰۰۰ رکورد را برای به دست آوردن ۵۰ رکورد بارگزاری میکنیم. در زیر روشی شرح داده میشود که توسط آن، این سربار اضافه از روی برنامه و سرورهای مربوطه حذف شود. به stored procedure و توضیحات مربوط به آن توجه فرمایید :
CREATE PROCEDURE sp_PagedItems ( @Page int, @RecsPerPage int ) AS -- We don't want to return the # of rows inserted -- into our temporary table, so turn NOCOUNT ON SET NOCOUNT ON --Create a temporary table CREATE TABLE #TempItems ( ID int IDENTITY, Name varchar(50), Price currency ) -- Insert the rows from tblItems into the temp. table INSERT INTO #TempItems (Name, Price) SELECT Name,Price FROM tblItem ORDER BY Price -- Find out the first and last record we want DECLARE @FirstRec int, @LastRec int SELECT @FirstRec = (@Page - 1) * @RecsPerPage SELECT @LastRec = (@Page * @RecsPerPage + 1) -- Now, return the set of paged records, plus, an indiciation of we -- have more records or not! SELECT *, MoreRecords = ( SELECT COUNT(*) FROM #TempItems TI WHERE TI.ID >= @LastRec ) FROM #TempItems WHERE ID > @FirstRec AND ID < @LastRec -- Turn NOCOUNT back OFF SET NOCOUNT OFF
در مرحله بعد شماره اولین و آخرین سطر مورد نظر را بر اساس پارامترهای ورودی محاسبه کرده و در متغیرهای @FirstRec و @LastRec میریزیم.
برای
استفاده از این کد فقط کافیست که پارامترهای ورودی را مقداردهی نمایید.
مثلا اگر میخواهید در یک کنترل Grid از آن استفاده کنید باید ابتدا یک
کوئری داشته باشید که تعداد کل سطرها را به شما بدهد و بر اساس این مقدار
تعداد صفحات مورد نظر را به دست آورید. پس از آن با کلیک روی هر کدام از
شماره صفحات آن را به عنوان مقدار به پارامتر مورد نظر بفرستید و از آن لذت
ببرید.
جواب: بله ولی بدون عواقب نیست.
بطور معمول در زمانی که تراکنشهای باز بر روی سرور دیتابیس وجود دارد و بانک کرش میکند، کرش ریکاوری، تراکنشهای باز را رول بک میکند. این امر مانع از اثرات پراکنده از تراکنشهای فعلی در پایگاه داده میباشد.
اگر لاگ در دسترس نباشد هنگامی که کار سرور SQL شروع میشود، پایگاه داده در حالت SUSPECT قرار داده میشود. در این مواقع تنها راه آن لاین نمودن بانک ( البته منظور آماده بکار نمودن بانک نیست) استفاده از حالت قابلیت تعمیر اورژانسی است که از نسخه 2005 افزده شده است که با ساخت یک فایل لاگ جدید و سپس اجرا نمودن DBCC CHECKDB با استفاده از REPAIR_ALLOW_DATA_LOSS انجام میشود.
مشکل اینجاست که اگر شما به همین طریق بخواهید ادامه بدهید و از این قابلیت اورژانسی استفاده کنید، مسلما امکان وجود دستهای از تغییرات در بانک که ممکن است در میانه اعمال بروز رسانی چند رکورد فروش در یک جدول نیمی از تراکنشهای آنها روی بانک اعمال شده و بعد از راه اندازی دوباره با لاگ جدید، امکان برگرداندن آنها وجود داشته باشد، مواجه شوید. به این معنی که در بهترین حالت، پایگاه داده بدست آمده با برنامه هماهنگ نیست و یا اینکه به بک آپی که وجود دارد رضایت بدهید.
حالت تعمیر اورژانسی، زمانیکه همه راهحلهای بازگردانی بانک مغلوب شوند آخرین روش مانده است. این حالت، حالتی بین دو شر، ریکاور کردن بانک به حالتی نا هماهنگ با نرم افزار و یا برگرداندن به زمانی خیلی عقبتر است که در نهایت اقدام به انجام هر دو و هماهنگتر کردن بانک برای کار با برنامه میشوید که بسیار وقت گیر و مشکل ساز است.
ولی باز ممکن است این وضعیت پیش بیاد چون شما از اتفاقی که در زمان کرش در بانک افتاده اطلاعی ندارید. برای جلوگیری از این موارد در آینده سازوکار بک آپ گیری از بانک را باید تغییر بدهید و دفعات بک آپ گیری را افزایش بدهید و نیز از روشهای جدید که قابلیت دسترسی بالا دارند استفاده کنید همانند mirroring و SQL Server 2012 Availability Groups است.
لینکهای مرتبط:
- اگر از attribute routing استفاده میکنید، متد MapControllers را فراخوانی کنید که البته به صورت ضمنی هم فراخوانی میشود.
- متد MapRoute پیشین به MapControllerRoute تغییر نام یافتهاست.
- متد MapAreaRoute پیشین به MapAreaControllerRoute تغییر نام یافتهاست.
علت این نوع نامگذاریهای صریح، این است که اکنون سیستم مسیریابی، فراتر از سیستم MVC را پشتیبانی میکند.
- همانند سابق متدهای MapControllerRoute/MapAreaControllerRoute/MapDefaultControllerRoute به ترتیبی که تعریف میشوند، تاثیرگذار خواهند بود.
در مثال زیر:
public void Configure(IApplicationBuilder app) { ... app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapAreaControllerRoute( "admin", "admin", "Admin/{controller=Home}/{action=Index}/{id?}"); endpoints.MapControllerRoute( "default", "{controller=Home}/{action=Index}/{id?}"); }); }
- متد MapAreaControllerRoute، مسیریابی سنتی کنترلرهای درون یک Area را تعریف میکند.
- متد MapControllerRoute، مسیریابی سنتی کنترلرها را تعریف میکند.
اگر از Razor pages استفاده میکنید، در اینجا باید متد MapRazorPages را نیز فراخوانی کنید:
public void Configure(IApplicationBuilder app) { ... app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); }); }
OutputCache در ASP.NET MVC
[ OutputCache (CacheProfile = "FirstPageIndex" ,Location=OutputCacheLocation.Server) ]
"Response. RemoveOutputCacheItem (Url. Action ( "index" , "home
و سوال دوم میخواهم کش برای مرورگر قرار بدم تا زمانیکه کاربر مهمان وارد سایت شد و استان و شهر خودشو انتخاب کرد، تا زمانیکه کاربر مهمان خودش نرفته شهرش رو عوض نکرده، تو همون شهر بمونه ( حتی با بستن مرورگر ) و بر اساس اون کش دیتابیس مربوط به استان خودشو نشون بده ( برای هر استان یک دیتابیس دارم ). فقط میدونم باید از کش مرورگر استفاده کرد. میشه یه توضیحی بدید ممنون میشم ( به خصوص امنیت این کاری که میخواهم بکنم ).
@using (Html.BeginForm("Multiple", "Home", FormMethod.Post, new { enctype = "multipart/form-data" })) { <input type="file" name="files" multiple /> <button class="btn btn-default">Upload</button> }
@using (Html.BeginForm("Multiple", "Home", FormMethod.Post, new { enctype = "multipart/form-data" })) { <input type="file" name="files" /><br /> <input type="file" name="files" /><br /> <input type="file" name="files" /><br /> <button class="btn btn-default">Upload</button> }
[HttpPost] public ActionResult Multiple(IEnumerable<HttpPostedFileBase> files) { foreach (var file in files) { if (file != null && file.ContentLength > 0) { file.SaveAs(Path.Combine(Server.MapPath("/uploads"), Guid.NewGuid() + Path.GetExtension(file.FileName))); } } return View(); }
مدلهای برنامه
در اینجا قصد داریم لیست گروهها را به همراه محصولات مرتبط با آنها، توسط دو drop down list نمایش دهیم:
public class Category { public int CategoryId { set; get; } public string CategoryName { set; get; } [JsonIgnore] public IList<Product> Products { set; get; } } public class Product { public int ProductId { set; get; } public string ProductName { set; get; } }
منبع داده JSON سمت سرور
پس از مشخص شدن مدلهای برنامه، اکنون توسط دو اکشن متد، لیست گروهها و همچنین لیست محصولات یک گروه خاص را با فرمت JSON بازگشت میدهیم:
using System.Linq; using System.Text; using System.Web.Mvc; using KendoUI12.Models; using Newtonsoft.Json; namespace KendoUI12.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); // shows the page. } [HttpGet] public ActionResult GetCategories() { return new ContentResult { Content = JsonConvert.SerializeObject(CategoriesDataSource.Items), ContentType = "application/json", ContentEncoding = Encoding.UTF8 }; } [HttpGet] public ActionResult GetProducts(int categoryId) { var products = CategoriesDataSource.Items .Where(category => category.CategoryId == categoryId) .SelectMany(category => category.Products) .ToList(); return new ContentResult { Content = JsonConvert.SerializeObject(products), ContentType = "application/json", ContentEncoding = Encoding.UTF8 }; } } }
در اینجا به عمد از JsonConvert.SerializeObject استفاده شدهاست تا ویژگی JsonIgnore کلاس گروهها، توسط کتابخانهی JSON.NET مورد استفاده قرار گیرد (ASP.NET MVC برخلاف ASP.NET Web API به صورت پیش فرض از JSON.NET استفاده نمیکند).
کدهای سمت کاربر برنامه
کدهای جاوا اسکریپتی Kendo UI را جهت تعریف دو drop down list به هم مرتبط و آبشاری، در ادامه ملاحظه میکنید:
<!--نحوهی راست به چپ سازی --> <div class="k-rtl k-header demo-section"> <label for="categories">گروهها: </label><input id="categories" style="width: 270px" /> <label for="products">محصولات: </label><input id="products" disabled="disabled" style="width: 270px" /> </div> @section JavaScript { <script type="text/javascript"> $(function () { $("#categories").kendoDropDownList({ optionLabel: "انتخاب گروه...", dataTextField: "CategoryName", dataValueField: "CategoryId", dataSource: { transport: { read: { url: "@Url.Action("GetCategories", "Home")", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET' } } } }); $("#products").kendoDropDownList({ autoBind: false, // won’t try and read from the DataSource when it first loads cascadeFrom: "categories", // the id of the DropDown you want to cascade from optionLabel: "انتخاب محصول...", dataTextField: "ProductName", dataValueField: "ProductId", dataSource: { // When the serverFiltering is disabled, then the combobox will not make any additional requests to the server. serverFiltering: true, // the DataSource will send filter values to the server transport: { read: { url: "@Url.Action("GetProducts", "Home")", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET', data: function () { return { categoryId: $("#categories").val() }; } } } } }); }); </script> <style scoped> .demo-section { width: 100%; height: 100px; } </style> }
سپس دراپ دوم که وابستهاست به دراپ داون اول، با این نکات طراحی شدهاست:
الف) خاصیت autoBind آن به false تنظیم شدهاست. به این ترتیب این دراپ داون در اولین بار نمایش صفحه، به سرور جهت دریافت اطلاعات مراجعه نخواهد کرد.
ب) خاصیت cascadeFrom آن به id دراپ داون اول تنظیم شدهاست.
ج) در منبع دادهی آن دو تغییر مهم وجود دارند:
- خاصیت serverFiltering به true تنظیم شدهاست. این مورد سبب خواهد شد تا آیتم گروه انتخاب شده، به سرور ارسال شود.
- خاصیت data نیز تنظیم شدهاست. این مورد پارامتر categoryId اکشن متد GetProducts را تامین میکند و مقدار آن از مقدار انتخاب شدهی دراپ داون اول دریافت میگردد.
اگر برنامه را اجرا کنیم، برای بار اول لیست گروهها دریافت خواهند شد:
سپس با انتخاب یک گروه، لیست محصولات مرتبط با آن در دراپ داون دوم ظاهر میگردند:
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.