context.CommandTimeout = 180;
برای رفع این مشکل مراحل زیر را انجام دهید:
- دو فایل tt موجود در مدل را حذف نمایید.
context.CommandTimeout = 180;
برای رفع این مشکل مراحل زیر را انجام دهید:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Security; namespace Myproject.Security { public class CustomRoleProvider : RoleProvider { } }
public override string[] GetRolesForUser(string username) { using (DatabaseEntities db = new DatabaseEntities()) { User user = db.Users.FirstOrDefault(u => u.UserName.Equals(username, StringComparison.CurrentCultureIgnoreCase)); var roles = from ur in user.UserRoles from r in db.Roles where ur.RoleId == r.Id select r.Name; if (roles != null) return roles.ToArray(); else return new string[] {}; ; } }
public override bool IsUserInRole(string username, string roleName) { using (DatabaseEntities db = new DatabaseEntities()) { User user = db.Users.FirstOrDefault(u => u.UserName.Equals(username, StringComparison.CurrentCultureIgnoreCase)); var roles = from ur in user.UserRoles from r in db.Roles where ur.RoleId == r.Id select r.Name; if (user != null) return roles.Any(r => r.Equals(roleName, StringComparison.CurrentCultureIgnoreCase)); else return false; } }
public override bool IsUserInRole(string username, string roleName) { return this.GetRolesForUser(username).Contains(roleName); }
<system.web> ... <rolemanager cacherolesincookie="true" defaultprovider="CustomRoleProvider" enabled="true"> <providers> <clear /> <add name="CustomRoleProvider" type="Myproject.Security.CustomRoleProvider" /> </providers> </rolemanager> ... </system.web>
using System; using System.Web.Mvc; namespace MyProject.Areas.Admin.Controllers { [Authorize(Roles = "Administrators")] public class HomeController : Controller { // // GET: /Admin/Home/ public ActionResult Index() { return View(); } } }
public class RemoveWhitespacesAttribute : ActionFilterAttribute { public override void OnActionExecuted(ActionExecutedContext filterContext) { var response = filterContext.HttpContext.Response; if (filterContext.HttpContext.Request.RawUrl != "/sitemap.xml") { if (response.ContentType == "text/html" && response.Filter != null) { response.Filter = new HelperClass(response.Filter); } } } private class HelperClass : Stream { private System.IO.Stream Base; public HelperClass(System.IO.Stream ResponseStream) { if (ResponseStream == null) throw new ArgumentNullException("ResponseStream"); this.Base = ResponseStream; } StringBuilder s = new StringBuilder(); public override void Write(byte[] buffer, int offset, int count) { string HTML = Encoding.UTF8.GetString(buffer, offset, count); Regex reg = new Regex(@"(?<=\s)\s+(?![^<>]*</pre>)"); HTML = reg.Replace(HTML, string.Empty); buffer = System.Text.Encoding.UTF8.GetBytes(HTML); this.Base.Write(buffer, 0, buffer.Length); } #region Other Members public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } public override bool CanRead { get { return false; } } public override bool CanSeek { get { return false; } } public override bool CanWrite { get { return true; } } public override long Length { get { throw new NotSupportedException(); } } public override long Position { get { throw new NotSupportedException(); } set { throw new NotSupportedException(); } } public override void Flush() { Base.Flush(); } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } public override void SetLength(long value) { throw new NotSupportedException(); } #endregion } }
protected void Application_Start() { try { GlobalFilters.Filters.Add(new App_Start.RemoveWhitespacesAttribute()); } catch { HttpRuntime.UnloadAppDomain(); // سبب ری استارت برنامه و آغاز مجدد آن با درخواست بعدی میشود throw; } }
برای Gzip هم اکثر در این حالت که هردو مورد با هم قرار داده شده است در برخی از موارد فایلهای جاواسکریپ را با مشکل روبرو میکند .به نظر من از Gzip توکار IIS استفاده شود بهتر است. البته باید ماژول آن در ISS فعال شده باشد.
برای اینکار هم داخل Web.config کدهای زیر را داخل configuration قرار بدید.
<httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files"> <scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" staticCompressionLevel="9" /> <dynamicTypes> <add mimeType="text/*" enabled="true" /> <add mimeType="message/*" enabled="true" /> <add mimeType="application/x-javascript" enabled="true" /> <add mimeType="application/javascript" enabled="true" /> <add mimeType="application/json" enabled="true" /> <add mimeType="application/json; charset=utf-8" enabled="true" /> <add mimeType="application/atom+xml" enabled="true" /> <add mimeType="application/xaml+xml" enabled="true" /> <add mimeType="*/*" enabled="false" /> </dynamicTypes> <staticTypes> <add mimeType="text/*" enabled="true" /> <add mimeType="message/*" enabled="true" /> <add mimeType="application/x-javascript" enabled="true" /> <add mimeType="application/javascript" enabled="true" /> <add mimeType="application/json" enabled="true" /> <add mimeType="application/json; charset=utf-8" enabled="true" /> <add mimeType="application/atom+xml" enabled="true" /> <add mimeType="application/xaml+xml" enabled="true" /> <add mimeType="*/*" enabled="false" /> </staticTypes> </httpCompression> <urlCompression doStaticCompression="true" doDynamicCompression="true" /> </system.webServer> <location path="Default Web Site"> <system.webServer> <serverRuntime enabled="true" frequentHitThreshold="1" frequentHitTimePeriod="10:00:00" /> </system.webServer> </location>
namespace Working { public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //app.Run(async (context) => //{ // await context.Response.WriteAsync("Hello World!"); //}); } } }
namespace Working.Models { public class Product { public string Name { get; set; } public decimal Price { get; set; } } }
namespace WorkingWithVisualStudio.Models { public class SimpleRepository { private static SimpleRepository sharedRepository = new SimpleRepository(); private Dictionary<string, Product> products = new Dictionary<string, Product>(); public static SimpleRepository SharedRepository => sharedRepository; public SimpleRepository() { var initialItems = new[] { new Product { Name = "Kayak", Price = 275M }, new Product { Name = "Lifejacket", Price = 48.95M }, new Product { Name = "Soccer ball", Price = 19.50M }, new Product { Name = "Corner flag", Price = 34.95M } }; foreach (var p in initialItems) { AddProduct(p); } } public IEnumerable<Product> Products => products.Values; public void AddProduct(Product p) => products.Add(p.Name, p); } }
namespace WorkingWithVisualStudio.Controllers { public class HomeController : Controller { public IActionResult Index() => View(SimpleRepository.SharedRepository.Products); } }
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>>Working with Visual Studio</title> </head> <body> <table> <thead> <tr> <td>Name</td> <td>Price</td> </tr> </thead> <tbody> @foreach (var p in Model) {<tr> <td>@p.Name</td> <td>@p.Price</td> </tr>} </tbody> </table> </body> </html>
<ItemGroup> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> </ItemGroup>
{ "name": "asp.net", "private": true, "dependencies": {} }
{ "name": "asp.net", "private": true, "resolutions": { "jquery": "3.3.1" } }
فرمت | توضیحات |
3.3.7 | بیان شماره مستقیم بسته نصب شده و تطبیق دقیق آن با شمار نسخه ، e.g ، 3.3.7 |
* | با استفاده از یک ستاره به Bower اجازه نصب آخرین نسخه را میدهد |
3.3.7 =<3.3.7< | پیشوند یک شماره نسخه با < یا =< به Bower اجازه میدهد تا هر نسخه از بستهای که بزرگتر یا بزرگتر مساوی آن نسخهی معین است، نصب شود |
3.3.7 =>3.3.7> | پیشوند یک شماره نسخه با > یا => به Bower اجازه میدهد تا هر نسخه از بستهای را که کوچکتر یا کوچکتر و مساوی نسخهی معین است، نصب شود |
3.3.7~ | پیشوند یک شماره نسخه با یک tilde (با کاراکتر ~ ) به نسخههایی که دو شماره
اولیه آنها مشابه باشند، اجازه نصب میدهد؛ حتی اگر شماره آخر آن نسخه متفاوت
باشد. مانند نسخههای 3.3.9 و 3.3.8 و اجازه نصب نسخه 3.4.0 را نمیدهد؛ چون
شماره دوم آن متفاوت است. |
3.3.7^ | پیشوند یک شماره نسخه با یک قلم (کاراکتر ^) به نسخههایی که شماره اول آنها مشابه باشند، اجازه نصب میدهد؛ حتی اگر شماره دوم آنها متفاوت باشد. مانند نسخههای 3.3.1 و 3.4.1 و 3.5.1 اما نسخه 4.0.0 اجازه نصب ندارد |
{ "name": "asp.net", "private": true, "dependencies": { "bootstrap": "4.0.0-alpha.6" } }
@model IEnumerable<WorkingWithVisualStudio.Models.Product> @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>>Working with Visual Studio</title> </head> <body> <h3>Products</h3> <table> <thead> <tr> <td>Name</td> <td>Price</td> </tr> </thead> <tbody> @foreach (var p in Model) { <tr> <td>@p.Name</td> <td>@($"{p.Price:C2}")</td> </tr>} </tbody> </table> </body> </html>
namespace WorkingWithVisualStudio.Controllers { public class HomeController : Controller { public IActionResult Index() => View(SimpleRepository.SharedRepository.Products .Where(p => p.Price < 50)); } }
namespace WorkingWithVisualStudio.Models { public class SimpleRepository { private static SimpleRepository sharedRepository = new SimpleRepository(); private Dictionary<string, Product> products = new Dictionary<string, Product>(); public static SimpleRepository SharedRepository => sharedRepository; public SimpleRepository() { var initialItems = new[] { new Product { Name = "Kayak", Price = 275M }, new Product { Name = "Lifejacket", Price = 48.95M }, new Product { Name = "Soccer ball", Price = 19.50M }, new Product { Name = "Corner flag", Price = 34.95M } }; foreach (var p in initialItems) { AddProduct(p); } products.Add("Error", null); } public IEnumerable<Product> Products => products.Values; public void AddProduct(Product p) => products.Add(p.Name, p); } }
namespace WorkingWithVisualStudio { public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseDeveloperExceptionPage(); } } } }
namespace WorkingWithVisualStudio { public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); app.UseDeveloperExceptionPage(); } app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } } }
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>>Working with Visual Studio</title> </head> <body> <h3>Products</h3> <table> <thead> <tr><td>Name</td><td>Price</td></tr> </thead> <tbody> <tr><td>Lifejacket</td><td>£48.95</td></tr> <tr><td>Soccer ball</td><td>£19.50</td></tr> <tr><td>Corner flag</td><td>£34.95</td></tr> </tbody> </table> <!-- Visual Studio Browser Link --> <script type="application/json" id="__browserLink_initializationData"> {"requestId":"968949d8affc47c4a9c6326de21dfa03","requestMappingFromServer":false} </script> <script type="text/javascript" src="http://localhost:55356/d1a038413c804e178ef009a3be07b262/browserLink" async="async"></script> <!-- End Browser Link --> </body> </html>
@model IEnumerable<WorkingWithVisualStudio.Models.Product> @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>>Working with Visual Studio</title> </head> <body> <h3>Products</h3> <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p> <table> <thead> <tr> <td>Name</td> <td>Price</td> </tr> </thead> <tbody> @foreach (var p in Model) { <tr> <td>@p.Name</td> <td>@($"{p.Price:C2}")</td> </tr>} </tbody> </table> </body> </html>
namespace WorkingWithVisualStudio { public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); app.UseStaticFiles(); app.UseDeveloperExceptionPage(); } app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } } }
h3 { } table, td { border: 2px solid black; border-collapse: collapse; padding: 5px; }
document.addEventListener("DOMContentLoaded", function () { var element = document.createElement("p"); element.textContent = "This is the element from the third.js file"; document.querySelector("body").appendChild(element); });
document.addEventListener("DOMContentLoaded", function () { var element = document.createElement("p"); element.textContent = "This is the element from the fourth.js file"; document.querySelector("body").appendChild(element); });
@model IEnumerable<WorkingWithVisualStudio.Models.Product> @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>>Working with Visual Studio</title> <link rel="stylesheet" href="css/first.css" /> <link rel="stylesheet" href="css/second.css" /> <script src="js/third.js"></script> <script src="js/fourth.js"></script> </head> <body> <h3>Products</h3> <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p> <table> <thead> <tr> <td>Name</td> <td>Price</td> </tr> </thead> <tbody> @foreach (var p in Model) { <tr> <td>@p.Name</td> <td>@($"{p.Price:C2}")</td> </tr>} </tbody> </table> </body> </html>
@model IEnumerable<WorkingWithVisualStudio.Models.Product> @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>>Working with Visual Studio</title> <link rel="stylesheet" href="css/bundle.min.css" /> <script src="js/bundle.min.js"></script> </head> <body> <h3>Products</h3> <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p> <table> <thead> <tr> <td>Name</td> <td>Price</td> </tr> </thead> <tbody> @foreach (var p in Model) { <tr> <td>@p.Name</td> <td>@($"{p.Price:C2}")</td> </tr>} </tbody> </table> </body> </html>
[ { "outputFileName": "Views/wwwroot/css/bundle.css", "inputFiles": [ "Views/wwwroot/css/First.css", "Views/wwwroot/css/second.css" ] }, { "outputFileName": "Views/wwwroot/js/bundle.js", "inputFiles": [ "Views/wwwroot/js/fourth.js", "Views/wwwroot/js/third.js" ] } ]
using System;
namespace MvcApplication7.Models
{
public class User
{
public int Id { set; get; }
public string Name { set; get; }
public string Password { set; get; }
public DateTime AddDate { set; get; }
public bool IsAdmin { set; get; }
}
}
using System;
using System.Collections.Generic;
namespace MvcApplication7.Models
{
public class Users
{
public IList<User> CreateInMemoryDataSource()
{
return new[]
{
new User { Id = 1, Name = "User1", Password = "123", IsAdmin = false, AddDate = DateTime.Now },
new User { Id = 2, Name = "User2", Password = "456", IsAdmin = false, AddDate = DateTime.Now },
new User { Id = 3, Name = "User3", Password = "789", IsAdmin = true, AddDate = DateTime.Now }
};
}
}
}
using System;
using System.Linq;
using System.Web.Mvc;
using MvcApplication7.Models;
namespace MvcApplication7.Controllers
{
public class UserController : Controller
{
[HttpGet]
public ActionResult Index()
{
var usersList = new Users().CreateInMemoryDataSource();
return View(usersList); // Shows the Index view.
}
[HttpGet]
public ActionResult Details(int id)
{
var user = new Users().CreateInMemoryDataSource().FirstOrDefault(x => x.Id == id);
if (user == null)
return View("Error");
return View(user); // Shows the Details view.
}
[HttpGet]
public ActionResult Create()
{
var user = new User { AddDate = DateTime.Now };
return View(user); // Shows the Create view.
}
[HttpPost]
public ActionResult Create(User user)
{
if (this.ModelState.IsValid)
{
// todo: Add record
return RedirectToAction("Index");
}
return View(user); // Shows the Create view again.
}
[HttpGet]
public ActionResult Edit(int id)
{
var user = new Users().CreateInMemoryDataSource().FirstOrDefault(x => x.Id == id);
if (user == null)
return View("Error");
return View(user); // Shows the Edit view.
}
[HttpPost]
public ActionResult Edit(User user)
{
if (this.ModelState.IsValid)
{
// todo: Edit record
return RedirectToAction("Index");
}
return View(user); // Shows the Edit view again.
}
[HttpPost]
public ActionResult Delete(int id)
{
// todo: Delete record
return RedirectToAction("Index");
}
}
}
@Html.ActionLink("Delete", "Delete", new { id=item.Id }
@using (Html.BeginForm(actionName: "Delete", controllerName: "User", routeValues: new { id = item.Id }))
{
<input type="submit" value="Delete"
onclick="return confirm ('Do you want to delete this record?');" />
}
@Html.ValidationSummary(true)
ModelState.AddModelError(string.Empty, "There is something wrong with model.");
@Html.EditorFor(model => model.AddDate)
@Html.ValidationMessageFor(model => model.AddDate)
public ActionResult Edit([Bind(Include = "Name, Password")] User user)
[HttpPost]
public ActionResult Edit()
{
var user = new User();
if(TryUpdateModel(user, includeProperties: new[] { "Name", "Password" }))
{
// todo: Edit record
return RedirectToAction("Index");
}
return View(user); // Shows the Edit view again.
}
public ActionResult Edit([Bind(Exclude = "IsAdmin")] User user)
using System;
using System.Web.Mvc;
namespace MvcApplication7.Models
{
[Bind(Exclude = "IsAdmin")]
public class User
{
public int Id { set; get; }
public string Name { set; get; }
public string Password { set; get; }
public DateTime AddDate { set; get; }
public bool IsAdmin { set; get; }
}
}
using System;
using System.ComponentModel;
namespace MvcApplication7.Models
{
public class User
{
public int Id { set; get; }
public string Name { set; get; }
public string Password { set; get; }
public DateTime AddDate { set; get; }
[ReadOnly(true)]
public bool IsAdmin { set; get; }
}
}
public class MyViewModel
{
public SomeDomainModel1 Model1 { get; set; }
public SomeDomainModel2 Model2 { get; set; }
...
}
public class MyViewModel
{
public string SomeExtraField1 { get; set; }
public string SomeExtraField2 { get; set; }
public IEnumerable<SelectListItem> StateSelectList { get; set; }
// ...
public string PersonFullName { set; set; }
}
namespace MvcApplication7.Models
{
public class UserViewModel
{
public int Id { set; get; }
public string Password { set; get; }
}
}
[HttpGet]
public ActionResult Upload()
{
return View(); // Shows the upload page
}
[HttpPost]
public ActionResult Upload(System.Web.HttpPostedFileBase file)
{
string filename = Server.MapPath("~/files/somename.ext");
file.SaveAs(filename);
return RedirectToAction("Index");
}
@{
ViewBag.Title = "Upload";
}
<h2>
Upload</h2>
@using (Html.BeginForm(actionName: "Upload", controllerName: "User",
method: FormMethod.Post,
htmlAttributes: new { enctype = "multipart/form-data" }))
{
<text>Upload a photo:</text> <input type="file" name="photo" />
<input type="submit" value="Upload" />
}
using System;
using System.Globalization;
using System.Web.Mvc;
namespace MvcApplication7.Binders
{
public class PersianDateModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var modelState = new ModelState { Value = valueResult };
object actualValue = null;
try
{
var parts = valueResult.AttemptedValue.Split('/'); //ex. 1391/1/19
if (parts.Length != 3) return null;
int year = int.Parse(parts[0]);
int month = int.Parse(parts[1]);
int day = int.Parse(parts[2]);
actualValue = new DateTime(year, month, day, new PersianCalendar());
}
catch (FormatException e)
{
modelState.Errors.Add(e);
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return actualValue;
}
}
}
ModelBinders.Binders.Add(typeof(DateTime), new PersianDateModelBinder());
public ActionResult Create([ModelBinder(typeof(PersianDateModelBinder))] User user)
[ModelBinder(typeof(PersianDateModelBinder))]
public class User
{
using System.Collections.Generic; namespace AutoMapperComparison.Models { public class User { public int Id { get; set; } public string Name { get; set; } public ICollection<Address> Addresses { get; set; } } }
using System.ComponentModel.DataAnnotations.Schema; namespace AutoMapperComparison.Models { public class Address { public int Id { get; set; } public double? Code { get; set; } public string Title { get; set; } public int UserId { get; set; } [ForeignKey(nameof(UserId))] public virtual User User { get; set; } } }
using EntityFramework.BulkInsert.Extensions; using System.Collections.Generic; using System.Data.Entity; using System.Data.SqlClient; namespace AutoMapperComparison.Models { public class AppDbContextInitializer : DropCreateDatabaseAlways<AppDbContext> { protected override void Seed(AppDbContext context) { User user = context.Users.Add(new User { Name = "Test" }); context.SaveChanges(); List<Address> addresses = new List<Address>(); for (int i = 0; i < 500000; i++) { addresses.Add(new Address { Id = i, Code = 1, Title = "Test", UserId = user.Id }); } context.BulkInsert(addresses); base.Seed(context); } } public class AppDbContext : DbContext { static AppDbContext() { Database.SetInitializer(new AppDbContextInitializer()); //Database.SetInitializer<AppDbContext>(null); } public AppDbContext() : base(new SqlConnection(@"Data Source=.;Initial Catalog=AppDbContext;Integrated Security=True"), contextOwnsConnection: true) { Configuration.AutoDetectChangesEnabled = false; Configuration.EnsureTransactionsForFunctionsAndCommands = false; Configuration.LazyLoadingEnabled = false; Configuration.ProxyCreationEnabled = false; Configuration.ValidateOnSaveEnabled = false; Configuration.UseDatabaseNullSemantics = false; } public DbSet<User> Users { get; set; } public DbSet<Address> Addresses { get; set; } } }
namespace AutoMapperComparison.Models { public class AddressDto { public int Id { get; set; } public double? Code { get; set; } public string Title { get; set; } public int UserId { get; set; } public string UserName { get; set; } } }
using AutoMapper; using AutoMapper.QueryableExtensions; using AutoMapperComparison.Models; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; namespace AutoMapperComparison { public class Program { public static void Main() { Mapper.Initialize(cfg => { cfg.CreateMap<Address, AddressDto>(); }); Console.WriteLine($"Create Db {DateTimeOffset.UtcNow}"); using (AppDbContext db = new AppDbContext()) { db.Database.Initialize(force: true); db.Database.ExecuteSqlCommand("DBCC DROPCLEANBUFFERS"); //Removes all clean buffers from the buffer pool, and columnstore objects from the columnstore object pool Console.WriteLine(db.Addresses.ProjectTo<AddressDto>()); Console.WriteLine(db.Addresses.Select(add => new AddressDto { Id = add.Id, Code = add.Code, Title = add.Title, UserId = add.UserId, UserName = add.User.Name })); } Console.WriteLine($"Normal Select {DateTimeOffset.UtcNow}"); using (AppDbContext db = new AppDbContext()) { db.Database.ExecuteSqlCommand("DBCC DROPCLEANBUFFERS"); Stopwatch watch = Stopwatch.StartNew(); List<AddressDto> addresses = db.Addresses.AsNoTracking().Select(add => new AddressDto { Id = add.Id, Code = add.Code, Title = add.Title, UserId = add.UserId, UserName = add.User.Name }).ToList(); List<AddressDto> addresses2 = db.Addresses.AsNoTracking().Select(add => new AddressDto { Id = add.Id, Code = add.Code, Title = add.Title, UserId = add.UserId, UserName = add.User.Name }).ToList(); watch.Stop(); Console.WriteLine($"{watch.ElapsedMilliseconds} {addresses.Count} {addresses2.Count}"); } Console.WriteLine($"AutoMapper Exec {DateTimeOffset.UtcNow}"); using (AppDbContext db = new AppDbContext()) { db.Database.ExecuteSqlCommand("DBCC DROPCLEANBUFFERS"); Stopwatch watch = Stopwatch.StartNew(); List<AddressDto> addresses = db.Addresses.AsNoTracking().ProjectTo<AddressDto>().ToList(); List<AddressDto> addresses2 = db.Addresses.AsNoTracking().ProjectTo<AddressDto>().ToList(); watch.Stop(); Console.WriteLine($"{watch.ElapsedMilliseconds} {addresses.Count} {addresses2.Count}"); } Console.ReadKey(); } } }
حال نتایج بدست آمده، در قسمت پایینتر آن نمایان میشود:
البته نتیجهی این آزمایش بسته به سخت افزار سیستم شما ممکن است کمی متفاوت باشد.
در سه آزمایش دیگر به صورت متوالی نتیجهی زیر بدست آمد:
Normal | AutoMapper |
2451 | 2378 |
2120 | 2111 |
2202 | 2124 |
اگر این مقدار جزئی از تفاوت بین دو نوع مختلف آزمایش را مورد نظر نگیریم، میتوان گفت که هر دو روش نتیجهی کاملا یکسانی خواهند داشت. فقط با استفاده از AutoMapper کدهای کمتری نوشته شدهاست!
اما دلیل چیست؟ از آنجایی که ProjectTo از Dto به Model انجام شده و Lambda Expressionی که به سمت Entity Framework فرستاده شدهاست با روش Normal کاملا برابر است و بقیهی عملیات توسط EF انجام میشود، با قاطعیت میتوان گفت که هر دو روش ذکر شده از نظر Performance کاملا یکسان خواهند بود.
نکته: البته به این موضوع باید توجه شود که اگر همین آزمایش را بطور مثال با استفاده از یک Listی از رکوردهای درون Memory ساخته شده توسط خودمان انجام دهیم، آن موقع نتیجهی یکسانی نخواهیم داشت، به دلیل اینکه EFی دیگر وجود نخواهد داشت که مسئولیت بازگشت دادهها را بر عهده بگیرد. از آنجائیکه اکثر کارهایی که توقع داریم AutoMapper برای ما انجام دهد، توسط ORM بازگشت داده میشود، پس میتوان گفت نکتهی فوق تقریبا در دنیای واقعی رخ نخواهد داد و باعث مشکل نخواهد شد.
تکه کد زیر را در نظر بگیرید: یک Action معمولی در Asp.Net MVC که یک نام را دریافت کرده و یک کارمندرا ایجاد میکند:
public ActionResult CreateEmployee(string name) { try { ValidateName(name); // ادامه کدها return View("با موفقیت ثبت شد"); } catch (ValidationException ex) { return View("خطا", ex.Message); } } private void ValidateName(string name) { if (string.IsNullOrWhiteSpace(name)) throw new ValidationException("نام نمیتواند خالی باشد"); if (name.Length > 100) throw new ValidationException("نام نمیتواند طولانی باشد"); }
در این قطعه کد، در متد ValidateName، در صورت معتبر نبودن ورودی، یک Exception رخ میدهد و بلاک کد try/catch، این exception را دریافت کرده و خطای مناسبی را به کاربر نشان خواهد داد. تا اینجا ظاهرا همه چیز مرتب است و مشکلی ندارد! احتمالا کدهای مشابه به این کد را زیاد دیدهاید. در اینجا متد ValidateName، صادق نیست. در قسمت اول، در مورد Honesty صحبت کردیم. به عبارت سادهتر شما از امضای این متد نمیتوانید به نوع خروجی و کاری که قرار است انجام دهد، پی ببرید. در واقع شما همیشه باید پیاده سازی متد را گوشهای، در ذهن خود داشته باشید و برای اطمینان از کاری که متد انجام میدهد، همیشه باید به بدنهی متد برگردیم. اگر بهخاطر داشته باشید، توابع برنامه نویسی را به توابع ریاضی تشبیه کردیم. پس میتوانیم بگوییم:
به عبارت دیگر وقتی از exceptionها برای کنترل flow برنامه استفاده میکنید، مشابه کاری را انجام میدهید که دستور GOTO انجام میداد. این دستور در روشهای قبل از برنامه نویسی ساخت یافته وجود داشت و توسط یک دانشمند هلندی به نام آقای دیکسترا حذف شد. وقتی از دستور GOTO یا JUMP استفاده میکنیم، فهمیدن flow برنامه پیچیدگیهای زیادی را خواهد داشت. چراکه فراخوانی قطعههای کد و متدها، وابستگی شدیدی خواهند داشت و البته میتوان گفت استفاده از exceptionها برای کنترل جریان برنامه، میتوانند از GOTO هم بدتر باشند؛ چرا که exception میتواند از لایههای مختلف کد نیز عبور کند.
امیدوارم تا اینجا به یک عقیدهی مشترک رسیده باشیم. خوب راهکار چیست؟ تصور کنید که تکه کد بالا را به صورت زیر تبدیل کنیم:
public ActionResult CreateEmployee(string name) { string error = ValidateName(name); if (error != string.Empty) return View("خطا", error); // ادامه کدها return View("با موفقیت ثبت شد"); } private string ValidateName(string name) { if (string.IsNullOrWhiteSpace(name)) return "نام نمیتواند خالی باشد"; if (name.Length > 100) return "طول نام نمیتواند بیشتر از 100 کاراکتر باشد"; return string.Empty; }
با refactor ای که انجام دادیم، متد ValidateName را به یک تابع ریاضی تبدیل کردیم. به این معنا که هر آنچه را که از امضای متد، مشخص است، انجام میدهد و در این حالت چیزی مخفی نیست. توجه داشته باشید که این راهکار نهایی ما نیست و لطفا مقاله را تا انتها بخوانید!
با همهی بدیهایی که از Exceptionها گفتیم، با این حساب پس چه زمانی از آن استفاده کنیم؟
در توضیح مورد سوم، در اعتبار سنجی دادههای کاربر (Validation) انتظار دادهی نادرستی را میتوان داشت، پس نمیتوانیم آن را یک حالت استثنایی بدانیم. معماری زیر را در نظر بگیرید
دیتایی که به API ما ارسال خواهد شد، همیشه شامل عملیات Filter یا به عبارتی Validation است و از آنجایی که میتوان انتظار استفادهی نادرست یا دیتای نادرست را داشت، نمیتوانیم این را حالتی از استثنائات در نظر بگیریم؛ ولی بر خلاف آن، وقتی در دامین پروژه و ارتباط بین دامینهای مختلف، دیتایی رد و بدل میشود که معتبر نیست، میتوانیم آن را جزء استثناءها در نظر بگیریم. به مثال زیر دقت کنید:
public ActionResult UpdateEmployee(int employeeId, string name) { string error = ValidateName(name); if (error != string.Empty) return View("Error", error); Employee employee = GetEmployee(employeeId); employee.UpdateName(name); } public class Employee { public void UpdateName(string name){ if (name == null) throw new ArgumentNullException(); // ادامه کدها } }
در قطعه کد بالا تصور این است که کلاس Employee و متد UpdateName خارج از دامین میباشند. همانطورکه مشاهده میکنید، ما در action controller، از خالی نبودن نام اطمینان حاصل کردیم و سپس آن را به متد UpdateName ارجاع دادیم. ولی اگه به بدنهی متد UpdateName دقت کنید، میبینید که مجددا از خالی نبودن نام اطمینان حاصل کردهایم و در صورت خالی بودن، یک Exception را صادر میکنیم! به این مدل چک کردنها در دامینهای مختلف، معمولا guard clause گفته میشود و یک نوع قرارداد بین برنامه نویس هاست. اگر طبق تعریفی که بالاتر ارائه کردیم هم چک کنیم، میتوانیم حدس بزنیم که خالی بودن نام، نشان یک باگ در نرم افزار است!
تا اینجا متوجه شدیم که از exceptionها باید در شرایط استثنائی استفاده کنیم. خوب با توجه به این مساله، چه طور میتوانیم آنها را Handle کنیم؟ این سؤال ما را به مفهومی به نام fail fast میرساند. این مفهوم به ما میگوید:
برای درک هر چه بهتر این موضوع، بیایید به عکس این حالت نگاه کنیم؛ اصطلاحا Fail Silently.
متد زیر را ببینید:
public void ProcessItems(List<Item> items) { foreach (Item item in items) { try { Process(item); } catch (Exception ex) { Logger.Log(ex); } } }
در قطعه کد بالا، در نگاه اول احتمالا حس نرم افزار پایدارتر و بدون خطا را خواهیم داشت. اما در واقع اینطور نیست. احتمال اینکه خطا از چشم برنامه نویس به دور باشد و بعد از اجرا باعث شود که یکپارچگی دادهها را به هم بریزد وجود دارد. در واقع هیچ راهی برای زمانیکه این عملیات نباید انجام شود، در نظر گرفته نشدهاست. طبق صحبتهایی که بالاتر داشتیم، شرایط غیر منتظره، در واقع یک باگ در نرم افزار است و هیچ مزیتی در جلوگیری از وقوع این باگ بدون حل مشکل نیست!
به صور خلاصه مهمترین مزیت Fail Fast را میتوانیم به صورت زیر خلاصه کنیم:
در یکی از حالتهای زیر:
حالت دیگر در استفاه از کتابخانههای دیگران (3rd parties) است. به طور مثال در استفاده از EF ممکن است به دلیل عدم برقراری ارتباط با دیتابیس، خطایی را دریافت کنید. در این حالت با توجه به نکات فوق، با این استثنائات برخورد کنید:
این به این معنی میباشد که به صورت کلی همه نوع Exception ای را به صورت کلی نگیرید و نوع Exception اختصاصی را در بلاک catch قرار دهید. الان که قرار شد در بعضی از حالتها جلوی استثنائات را بگیریم، خوب است ببینیم چطور باید اینکار را انجام بدیم.
قطعه کد زیر را در نظر بگیرید:
public void CreateCustomer(string name) { Customer customer = new Customer(name); bool result = SaveCustomer(customer); if (!result) { MessageBox.Show("Error connecting to the database. Please try again later."); } } private bool SaveCustomer(Customer customer) { try { using (MyContext context = new MyContext()) { context.Customers.Add(customer); context.SaveChanges(); } return true; } catch (DbUpdateException ex) { if (ex.Message == "Unable to open the DB connection") return false; else throw; } }
همانطور که مشاهده میکنید، در حالتیکه خطایی از نوع DbUpdateException رخ میدهد، مقدار بازگشتی متد را برابر با false میکنیم. اما مشکلی که وجود دارد این است که اینکار به اندازهی کافی خوانا نیست. همچنین honest بودن متد را نقض کردهایم. به علاوه مشکل بزرگتر دیگر این است که ما با بازگرداندن یک مقدار bool، میتوانیم به متد بالاتر اطلاع بدهیم که کار مورد نظر انجام شده یا نه، اما در مورد دلیل انجام نشدن آن، هیچ کاری نمیتوانیم بکنیم. پیشنهاد من برای مقدار بازگشتی متدهایی که احتمال انجام نشدن کاری در آنها میرود، استفاده از یک نوع اختصاصی میباشد.
در اینجا من این نوع را با نام کلاس Result معرفی میکنم. انتظاری که از این نوع اختصاصی داریم:
برای مثال کد بالا را به شکل زیر refactor میکنیم:
private Result SaveCustomer(Customer customer) { try { using (var context = new MyContext()) { context.Customers.Add(customer); context.SaveChanges(); } return Result.Ok(); } catch (DbUpdateException ex) { if (ex.Message == "Unable to open the DB connection") Result.Fail(ErrorType.DatabaseIsOffline); if (ex.Message.Contains("IX_Customer_Name")) return Result.Fail(ErrorType.CustomerAlreadyExists); throw; } }
به عبارتی با این روش میتوانیم از انجام شدن/نشدن عملیات اطمینان حاصل کنیم و خروجی/دلیل انجام نشدن را نیز میتوانیم برگردانیم.
اگر به امضای متدهای زیر نگاه کنیم، میتوانیم آنها را طبق الگوی CQS دستهبندی کنیم:
به عنوان نمونه یک پیاده سازی از این کلاس را در اینجا قرار دادهام. قطعا میتوانیم پیاده سازیهای بهتری را از این کلاس داشته باشیم. خوشحال میشوم که نظرات خود رو با ما به اشتراک بگذارید. امیدوارم که این قسمت و صحبتهایی که در مورد استثنائات داشتیم، توانسته باشد دیدگاه جدیدی را به کدهایتان بدهد. در ادامهی این سری مطالب، مفاهیم پارادایم برنامه نویسی تابعی را بیشتر مورد بررسی قرار خواهیم داد.
[DataContract] public class Employee { [DataMember] public string Name { get; set; } [DataMember] public Employee Manager { get; set; } }
[DataContract] public class Department { [DataMember] public string DeptName { get; set; } [DataMember] public List<Employee> Staff { get; set; } }
[ServiceContract] public interface IDepartmentService { [OperationContract] Department GetOneDepartment(); }
public class DepartmentService : IDepartmentService { public Department GetOneDepartment() { List<Employee> listOfEmployees = new List<Employee>(); var masoud = new Employee() { Name = "Masoud" }; var saeed = new Employee() { Name = "Saeed", Manager = masoud }; var peyman = new Employee() { Name = "Peyman", Manager = saeed }; var mostafa = new Employee() { Name = "Mostafa", Manager = saeed }; return new Department() { DeptName = "IT", Staff = new List<Employee>() { masoud, saeed, peyman, mostafa } }; } }
class Program { static void Main( string[] args ) { DepartmentServiceClient client = new DepartmentServiceClient(); var result = client.GetOneDepartment(); WriteDataToFile( result ); Console.ReadKey(); } private static void WriteDataToFile( Department data ) { DataContractSerializer dcs = new DataContractSerializer( typeof( Department ) ); var ms = new MemoryStream(); dcs.WriteObject( ms, data ); ms.Seek( 0, SeekOrigin.Begin ); var sr = new StreamReader( ms ); var xml = sr.ReadToEnd(); string filePath = @"d:\\data.xml"; if ( !File.Exists( filePath ) ) { File.Create( filePath ); } using ( TextWriter writer = new StreamWriter( filePath ) ) { writer.Write( xml ); } }
<Department xmlns="http://schemas.datacontract.org/2004/07/Service" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <Name>IT</Name> <Staff> <Employee> <Manager i:nil="true"/> <Name>Masoud</Name> </Employee> <Employee> <Manager> <Manager i:nil="true"/> <Name>Masoud</Name> </Manager> <Name>Saeed</Name> </Employee> <Employee> <Manager> <Manager> <Manager i:nil="true"/> <Name>Masoud</Name> </Manager> <Name>Saeed</Name> </Manager> <Name>Peyman</Name> </Employee> <Employee> <Manager> <Manager> <Manager i:nil="true"/> <Name>Masoud</Name> </Manager> <Name>Saeed</Name> </Manager> <Name>Mostafa</Name> </Employee> </Staff> </Department>
[DataContract( IsReference = true )] public class Employee { [DataMember] public string Name { get; set; } [DataMember] public Employee Manager { get; set; } }
<Department xmlns="http://schemas.datacontract.org/2004/07/Service" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <Name>IT</Name> <Staff> <Employee z:Id="i1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"> <Manager i:nil="true"/> <Name>Masoud</Name> </Employee> <Employee z:Id="i2" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"> <Manager z:Ref="i1"/> <Name>Saeed</Name> </Employee> <Employee z:Id="i3" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"> <Manager z:Ref="i2"/> <Name>Peyman</Name> </Employee> <Employee z:Id="i4" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"> <Manager z:Ref="i2"/> <Name>Mostafa</Name> </Employee> </Staff> </Department>
public IActionResult Get() { var userData = new User { Name = "Farhad", Family = "Zamani" }; _logger.LogInformation($"ValuesController called. Name:{userData.Name}, Family:{userData.Family}"); return Ok(); }
اگر بخواهیم مرتب سازی و یا فیلتری را بر روی Name و یا Family انجام دهیم، کار آسانی نخواهد بود و وقتگیر خواهد بود؛ زیرا باید با یک فیلد رشته ( message )، که تمامی لاگ درون آن قرار دارد، کار کنیم. اما با استفاده از قابلیت Structured Logging مربوط به Serilog، میتوانیم آبجکت userData را به ILogger پاس دهیم و پراپرتیهای آن را به صورت فیلدهای جدا نمایش دهیم.
برای این کار باید آبجکت موردنظر خود را درون {} قرار دهیم و قبل از نام متغییر آن یک @ قرار دهیم. بدین صورت: {userData@} و سپس دیتای موردنظر را در پارامتر دوم logger قرار دهیم.
_logger.LogInformation("ValuesController called. {@userData}", userData);
اگر کد نوشته شده مربوط به ثبت لاگ را به صورت زیر اصلاح کنیم:
public IActionResult Get() { var userData = new User { Name = "Farhad", Family = "Zamani" }; _logger.LogInformation("ValuesController called. {@userData}", userData); return Ok(); }
در پنل کیبانا به راحتی میتوان عملیات مرتب سازی و یا فیلتر را بر روی پراپرتیهای Name و Family انجام دهیم. اکنون اگر پنل کیبانا را تماشا کنید چنین لاگی را مشاهده میکنیم:
هرکدام از پراپرتیهای userData به صورت یک فیلد جدا ارسال شدهاست که به راحتی میتوانید مرتب سازی، فیلتر و... را بر روی هرکدام از فیلدها انجام دهید.
تنظیمات مربوط به Serilog:
public static void Main(string[] args) { var configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .Build(); Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() .Enrich.WithMachineName() .Filter.ByExcluding(Matching.FromSource("Serilog")) .Filter.ByExcluding(Matching.FromSource("System.Net.Http")) .Filter.ByExcluding(Matching.FromSource("Microsoft.AspNetCore")) .WriteTo.Console() .ReadFrom.Configuration(configuration) .CreateLogger(); CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseSerilog() .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
فایل appsettings.json:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "Serilog": { "WriteTo": [ { "Name": "Elasticsearch", "Args": { "nodeUris": "http://127.0.0.1:9200;", "indexFormat": "structuredlogging-{0:yyyy.MM}", "templateName": "structuredlogging" } } ] } }
فایل docker-compose برای اجرای Elasticsearch و Kibana:
version: '3' services: elasticsearch: container_name: elasticsearch image: elasticsearch:7.14.2 environment: - discovery.type=single-node ports: - 9200:9200 - 9300:9300 - 8200:8200 kibana: container_name: kibana image: kibana:7.14.2 ports: - 5601:5601
منابع استفاده شده:
<a rel="tooltip" title="یک سری توضیحات در اینجا" href="#">اطلاعات</a> <script type="text/javascript"> $(document).ready(function () { $("[rel='tooltip']").tooltip({placement:'top', trigger : 'hover'}); }); </script>
@model Mvc4TwitterBootStrapTest.Models.User @{ ViewBag.Title = "تعریف کاربر جدید"; } @using (Html.BeginForm()) { @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" }) <fieldset class="form-horizontal"> <legend>تعریف کاربر جدید</legend> <div class="control-group"> @Html.LabelFor(model => model.Name, new { @class = "control-label" }) <div class="controls"> @Html.EditorFor(model => model.Name) @*@Html.ValidationMessageFor(model => model.Name, null, new { @class = "help-inline" })*@ </div> </div> <div class="control-group"> @Html.LabelFor(model => model.LastName, new { @class = "control-label" }) <div class="controls"> @Html.EditorFor(model => model.LastName) @*@Html.ValidationMessageFor(model => model.LastName, null, new { @class = "help-inline" })*@ </div> </div> <div class="form-actions"> <button type="submit" class="btn btn-primary"> ارسال</button> </div> </fieldset> } @section JavaScript { <script type="text/javascript"> $.validator.setDefaults({ showErrors: function (errorMap, errorList) { this.defaultShowErrors(); //اگر المانی معتبر است نیاز به نمایش تولتیپ ندارد $("." + this.settings.validClass).tooltip("destroy"); //افزودن تولتیپها for (var i = 0; i < errorList.length; i++) { var error = errorList[i]; $(error.element).tooltip({ trigger: "focus" }) // فقط در حالت فوکوس نمایش داده شود .attr("data-original-title", error.message); } }, // همانند قبل برای رنگی کردن کل ردیف در صورت عدم اعتبار سنجی و برعکس highlight: function (element, errorClass, validClass) { if (element.type === 'radio') { this.findByName(element.name).addClass(errorClass).removeClass(validClass); } else { $(element).addClass(errorClass).removeClass(validClass); $(element).closest('.control-group').removeClass('success').addClass('error'); } $(element).trigger('highlited'); }, unhighlight: function (element, errorClass, validClass) { if (element.type === 'radio') { this.findByName(element.name).removeClass(errorClass).addClass(validClass); } else { $(element).removeClass(errorClass).addClass(validClass); $(element).closest('.control-group').removeClass('error').addClass('success'); } $(element).trigger('unhighlited'); } }); //برای حالت پست بک از سرور عمل میکند $(function () { $('form').each(function () { $(this).find('div.control-group').each(function () { if ($(this).find('span.field-validation-error').length > 0) { $(this).addClass('error'); } }); }); }); </script> }
<input class="text-box single-line" data-val="true" data-val-required="لطفا نام را تکمیل کنید" id="Name" name="Name" type="text" value="" />
<input class="text-box single-line input-validation-error" data-val="true" data-val-required="لطفا نام را تکمیل کنید" id="Name" name="Name" type="text" value="" data-original-title="لطفا نام را تکمیل کنید" title="">