A Gantt chart is mainly used in project management. It is a bar chart, designed by Henry Gantt, that shows a project schedule. Gantt chart illustrates elements contained in the work breakdown structure (WBS) of the project. Gantt charts usually show the start and finish dates of WBS elements and also show the dependency relationships between them.
وبلاگها و سایتهای ایرانی
Visual Studio
ASP. Net
طراحی وب
اسکیوال سرور
Nhibernate
عمومی دات نت
ویندوز
متفرقه
- JavaFX 1.0 منتشر شد. (بحث داغ این هفتهی مجامع جاوا بود)
لینکهای هفته سوم دی
وبلاگها ، سایتها و مقالات ایرانی (داخل و خارج از ایران)
ASP. Net
- ویدیویی دربارهی ASP.Net 4 (بهبودهای حاصل شده در web forms از جهت کار با اسکریپتها خصوصا با تاکید بر jQuery و همچنین ذکر اینکه با آمدن ASP.Net MVC ، وب فرمها کهنه نشده و همچنان توسعه و بهبود داده خواهند شد)، یا مقالهای در این مورد
طراحی و توسعه وب
PHP
اسکیوال سرور
سی شارپ
عمومی دات نت
مسایل اجتماعی و انسانی برنامه نویسی
متفرقه
در حال حاضر توصیهی مایکروسافت ، عدم استفادهی از System.Drawing.Common و جایگزینی آن با یکی از کتابخانههای زیر است:
- SkiaSharp
- Microsoft.Maui.Graphics
البته پیشتر در این لیست توصیه شده، کتابخانهی SixLabors.ImageSharp.Drawing هم وجود داشت که به علت تغییر مجوز آن، به یک مجوز نیمه تجاری، نیمه سورس باز، از لیست فوق حذف شدهاست.
مشکل فارسی نویسی با SkiaSharp
اگر سعی کنیم با استفاده از مثالهای متداول SkiaSharp، یک متن فارسی را نمایش دهیم، به خروجی زیر خواهیم رسید:
// crate a surface var info = new SKImageInfo(256, 256); using var surface = SKSurface.Create(info); // the the canvas and properties var canvas = surface.Canvas; // make sure the canvas is blank canvas.Clear(SKColors.White); // draw some text using var typeface = SKTypeface.FromFamilyName("Tahoma"); using var paint = new SKPaint { Color = SKColors.Black, IsAntialias = true, Style = SKPaintStyle.Fill, TextAlign = SKTextAlign.Center, TextSize = 24, Typeface = typeface, }; var coord = new SKPoint(info.Width / 2, (info.Height + paint.TextSize) / 2); canvas.DrawText("آزمایش", coord, paint); // save the file using var image = surface.Snapshot(); using var data = image.Encode(SKEncodedImageFormat.Png, 100); using var stream = File.OpenWrite("farsi-text-1.png"); data.SaveTo(stream);
<ItemGroup> <PackageReference Include="SkiaSharp" Version="2.88.3" /> </ItemGroup>
همانطور که مشاهده میکنید، حروف فارسی در آن از هم جدا هستند و همچنین از چپ به راست نمایش داده شدهاست.
رفع مشکل فارسی نویسی با SkiaSharp
برای رفع مشکل فوق، نیاز است از افزونهی «حرف باز» این کتابخانه استفاده کرد که روش نصب آن به صورت زیر است:
<ItemGroup> <PackageReference Include="SkiaSharp" Version="2.88.3" /> <PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.3" /> </ItemGroup>
// crate a surface var info = new SKImageInfo(256, 256); using var surface = SKSurface.Create(info); // the the canvas and properties var canvas = surface.Canvas; // make sure the canvas is blank canvas.Clear(SKColors.White); // draw some text using var typeface = SKTypeface.FromFamilyName("Tahoma"); using var shaper = new SKShaper(typeface); using var paint = new SKPaint { Color = SKColors.Black, IsAntialias = true, Style = SKPaintStyle.Fill, TextAlign = SKTextAlign.Center, TextSize = 24, Typeface = typeface, }; var coord = new SKPoint(info.Width / 2, (info.Height + paint.TextSize) / 2); canvas.DrawShapedText(shaper, "آزمایش", coord, paint); // save the file using var image = surface.Snapshot(); using var data = image.Encode(SKEncodedImageFormat.Png, 100); using var stream = File.OpenWrite("farsi-text-2.png"); data.SaveTo(stream);
کتابخانه jose-jwt
به این صورت مدلهای خود را تعریف کرده و طبق مقالهی قبلی، Controllerهای هر یک را پیاده سازی نمایید:
public class Supplier { [Key] public string Key {get; set; } public string Name { get; set; } } public class Category { public int ID { get; set; } public string Name { get; set; } public virtual ICollection<Product> Products { get; set; } } public class Product { public int ID { get; set; } public string Name { get; set; } public decimal Price { get; set; } [ForeignKey("Category")] public int CategoryId { get; set; } public Category Category { get; set; } [ForeignKey("Supplier")] public string SupplierId { get; set; } public virtual Supplier Supplier { get; set; } }
پکیج Microsoft.AspNet.OData به تازگی ورژن 6 آن به صورت رسمی منتشر شده و شامل تغییراتی نسبت به نسخهی قبلی آن است. اولین نکتهی حائز اهمیت، Config آن است که به صورت زیر تغییر کرده و باید Optionهای مورد نیاز، کانفیگ شوند. در این نسخه DI نیز به Odata اضافه شده است:
public static void Register(HttpConfiguration config) { ODataModelBuilder odataModelBuilder = new ODataConventionModelBuilder(); var product = odataModelBuilder.EntitySet<Product>("Products"); var category = odataModelBuilder.EntitySet<Category>("Categories"); var supplier = odataModelBuilder.EntitySet<Supplier>("Suppliers"); var edmModel = odataModelBuilder.GetEdmModel(); supplier.EntityType.Ignore(c => c.Name); config.Select(System.Web.OData.Query.QueryOptionSetting.Allowed); config.MaxTop(25); config.OrderBy(System.Web.OData.Query.QueryOptionSetting.Allowed); config.Count(System.Web.OData.Query.QueryOptionSetting.Allowed); config.Expand(System.Web.OData.Query.QueryOptionSetting.Allowed); config.Filter(System.Web.OData.Query.QueryOptionSetting.Allowed); config.Count(System.Web.OData.Query.QueryOptionSetting.Allowed); //config.MapODataServiceRoute("ODataRoute", "odata", edmModel); // کانفیگ به صورت معمولی config.MapODataServiceRoute("ODataRoute", "odata", builder => { builder.AddService(ServiceLifetime.Singleton, sp => edmModel); builder.AddService<IEnumerable<IODataRoutingConvention>>(ServiceLifetime.Singleton, sp => ODataRoutingConventions.CreateDefault()); }); }
[EnableQuery] public IQueryable<Product> Get() { return new List<Product> { new Product { Id = 1, Name = "name 1", Price = 11, Category = new Category {Id =1, Name = "Cat1" } }, new Product { Id = 2, Name = "name 2", Price = 12, Category = new Category {Id =2, Name = "Cat2" } }, new Product { Id = 3, Name = "name 3", Price = 13, Category = new Category {Id =3, Name = "Cat3" } }, new Product { Id = 4, Name = "name 4", Price = 14, Category = new Category {Id =4, Name = "Cat4" } }, }.AsQueryable(); ; }
Description | Option |
بسط دادن موجودیت مرتبط | $expand |
فیلتر کردن نتیجه، بر اساس شرطهای Boolean ی | $filter |
فرمان به سرور که تعداد رکوردهای بازگشتی را نیز نمایش دهد(مناسب برای پیاده سازی server-side pagging ) | $count |
مرتب کردن نتیجهی بازگشتی | $orderby |
select زدن روی پراپرتیهای درخواستی | $select |
پرش کردن از اولین رکورد به اندازهی n عدد | $skip |
فقط بازگرداندن n رکورد اول | $top |
/odata/Products?$select=Id,Name
/odata/Products?$top=3&$skip=2
/odata/Products?$count=true
{@odata.context: "http://localhost:4516/odata/$metadata#Products", @odata.count: 4,…} @odata.context:"http://localhost:4516/odata/$metadata#Products" @odata.count:4 value:[{Id: 1, Name: "name 1", Price: 11, SupplierId: 0, CategoryId: 0},…] 0:{Id: 1, Name: "name 1", Price: 11, SupplierId: 0, CategoryId: 0} 1:{Id: 2, Name: "name 2", Price: 12, SupplierId: 0, CategoryId: 0} 2:{Id: 3, Name: "name 3", Price: 13, SupplierId: 0, CategoryId: 0} 3:{Id: 4, Name: "name 4", Price: 14, SupplierId: 0, CategoryId: 0}
/odata/Products?$filter=Id eq 1
/odata/Products?$filter=Id gt 1 and Id lt 3
/odata/Products?$orderby=Id desc
/odata/Products?$expand=Category
/odata/Products?$expand=Category,Supplier
/odata/Categories(1)?$expand=Products/Supplier
[EnableQuery(PageSize = 10)]
محدود کردن Query Options
[EnableQuery (AllowedQueryOptions= AllowedQueryOptions.Skip | AllowedQueryOptions.Top)]
[EnableQuery(AllowedLogicalOperators=AllowedLogicalOperators.Equal)]
var product = odataModelBuilder.EntitySet<Product>("Products"); product.EntityType.Ignore(e => e.Price);
[EnableQuery(AllowedOrderByProperties = "Id,Name")]
public System.Web.Http.IHttpActionResult GetName(int key) { Product product = Get().Single(c => c.Id == key); return Ok(product.Name); }
/odata/Products(1)/Name/$value
HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 DataServiceVersion: 3.0 Content-Length: 3 Ali
Attribute Convention هایی هم برای اعتبارسنجی پراپرتیها موجود است که نام آنها واضح تعریف شدهاند:
Description | Attribute |
اجازهی فیلتر زدن بر روی آن پراپرتی داده نخواهد شد | NotFilterable |
اجازهی مرتب کردن بر روی آن پراپرتی داده نخواهد شد | NotSortable |
اجازهی select زدن بر روی آن پراپرتی داده نمیشود | NotNavigable |
اجازهی شمارش دهی بر روی آن Collection داده نمیشود | NotCountable |
اجازهی بسط دادن آن Collection داده نمیشود | NotExpandable |
و همچنین [AutoExpand] به صورت اتوماتیک آن موجودیت مورد نظر را بسط میدهد.
بطور مثال کدهای زیر را در مدل خود میتوانید مشاهده نمائید:
public class Product { public int Id { get; set; } public string Name { get; set; } [NotFilterable, NotSortable] public decimal Price { get; set; } [ForeignKey(nameof(SupplierId))] [NotNavigable] public virtual Supplier Supplier { get; set; } public int SupplierId { get; set; } [ForeignKey(nameof(CategoryId))] public virtual Category Category { get; set; } public int CategoryId { get; set; } [NotExpandable] public virtual ICollection<TestEntity> TestEnities { get; set; } }
فرض کنید پراپرتی زیر را به مدل خود اضافه کرده اید
public DateTimeOffset CreatedOn { get; set; }
حال کوئری زیر را برای فیلتر زدن، بر روی آن در اختیار داریم:
/odata/Products?$filter=year(CreatedOn) eq 2016
در اینجا فقط Product هایی بازگردانده میشوند که در سال 2016 ثبت شدهاند:
/odata/Products?$filter=CreatedOn lt cast(2017-04-01T04:11:31%2B08:00,Edm.DateTimeOffset)
کوئری فوق تاریخ مورد نظر را Cast کرده و همهی Product هایی را که قبل از این تاریخ ثبت شدهاند، باز میگرداند.
Nested Filter In Expand
/odata/Categories?$expand=Products($filter=Id gt 1 and Id lt 5)
همهی Categoryها به علاوه بسط دادن Product هایشان، در صورتیکه Id آنها بیشتر از 1 باشد
و یا حتی بر روی موجودیت بسط داده شده، select زده شود:
/odata/Categories?$expand=Products($select=Id,Name)
Custom Attribute
ضمنا به سادگی میتوان اتریبیوت سفارشی نوشت:
public class MyEnableQueryAttribute : EnableQueryAttribute { public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions) { // Don't apply Skip and Top. var ignoreQueryOptions = AllowedQueryOptions.Skip | AllowedQueryOptions.Top; return queryOptions.ApplyTo(queryable, ignoreQueryOptions); } }
روی هر متدی از کنترلر خود که اتریبیوت [MyEnableQuery] را قرار دهید، دیگر قابلیت Skip, Top را نخواهد داشت.
Dependency Injection در آخرین نسخهی OData اضافه شده است. بطور پیشفرض OData بصورت case-sensitive رفتار میکند. برای تغییر دادن آن در نسخههای قدیمی، Extension Methodی به نام EnableCaseSensitive وجود داشت. اما در نسخهی جدید شما میتوانید پیاده سازی خاص خود را از هر کدام از بخشهای OData داشته باشید و با استفاده از تزریق وابستگی، آن را به config برنامهی خود اضافه کنید؛ برای مثال:
public class CaseInsensitiveResolver : ODataUriResolver { private bool _enableCaseInsensitive; public override bool EnableCaseInsensitive { get { return true; } set { _enableCaseInsensitive = value; } } }
اینجا پیاده سازی از ODataUriResolver انجام شده و متد EnableCaseInsensitive به صورت جدیدی override و در حالت default مقدار true را برمیگرداند.
حال به صورت زیر آن را میتوان به وابستگیهای config برنامه، اضافه نمود:
config.MapODataServiceRoute("ODataRoute", "odata", builder => { builder.AddService(ServiceLifetime.Singleton, sp => edmModel); builder.AddService<IEnumerable<IODataRoutingConvention>>(ServiceLifetime.Singleton, sp => ODataRoutingConventions.CreateDefault()); builder.AddService<ODataUriResolver>(ServiceLifetime.Singleton, sp => new CaseInsensitiveResolver()); // how enable case sensitive });
در قسمت بعدی به Actionها و Functionها در OData میپردازیم.
public class Service { public int ServiceId { get; set; } public string ServiceName { get; set; } }
public interface ICoreService { Service LoadDefaultService(); }
An unhandled exception occurred while processing the request InvalidOperationException: Unable to resolve service for type 'WebApplication1.Models.ICoreService' while attempting to activate 'WebApplication1.Controllers.HomeController' Microsoft.Extensions.Internal.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
در نسخههای قدیمی MVC (منظور نسخههای قبل از 6)، برای تزریق وابستگیها از یک Controller Factory یا Dependency Resolver سفارشی استفاده میشد. اما در نسخه جدید MVC دیگری خبری از روشهای قدیمی نیست. چونکه یک سیستم تزریق وابستگی توکار، همراه با MVC یکپارچه شدهاست که عملیات تزریق وابستگیها را انجام میدهد. سیستم تزریق وابستگی پیش فرض، تنها از 4 حالت عملیاتی پشتیبانی میکند:
تیم Asp.Net برای فراهم آوردن امکان تزریق وابستگیها، تصمیم به انتزاعی کردن ویژگیهای مشترک محبوبترین Ioc Containerها و اجازه دادن به میان افزارها، جهت ارتباط با این اینترفیسها برای دستیابی به تزریق وابستگی بود.
namespace Microsoft.Extensions.DependencyInjection { // // Summary: // Specifies the lifetime of a service in an Microsoft.Extensions.DependencyInjection.IServiceCollection. public enum ServiceLifetime { // // Summary: // Specifies that a single instance of the service will be created. Singleton = 0, // // Summary: // Specifies that a new instance of the service will be created for each scope. // // Remarks: // In ASP.NET Core applications a scope is created around each server request. Scoped = 1, // // Summary: // Specifies that a new instance of the service will be created every time it is // requested. Transient = 2 } }
public void ConfigureServices(IServiceCollection services) { ServiceDescriptor descriptor = new ServiceDescriptor(typeof(ICoreService),typeof(CoreServise),ServiceLifetime.Transient); services.Add(descriptor); services.AddMvc(); }
ساخت یک Service Descriptor و اضافه کردن آن به سرویسها، فلسفه وجودی میان افزارها را زیر سوال میبرد. پس بجای ایجاد یک Service Descriptor، از متدهای الحاقی تدارک دیده شده استفاده میکنیم. مثلا بجای دو خط کد بالا میتوان از کد زیر استفاده نمود:
services.AddTransient<ICoreService,CoreServise>();
حال که یک دید کلی از نحوه کار مکانیزم تزریق وابستگی بدست آوردیم، میخواهیم این مکانیزم را با StructureMap جایگزین کنیم. بدین منظور ابتدا پکیج StructureMap را نصب میکنم.
در مرحله اول باید کلاسهایی را تدارک ببینیم که اینترفیسهای بالا را پیاده سازی نمایند. یعنی کلاسهای ما باید بتوانند همان کاری را انجام دهند که مکانیزم پیش فرض MVC انجام میدهد.
اولین مورد، کلاس StructureMapServiceProvider میباشد.
internal class StructureMapServiceProvider : IServiceProvider { private readonly IContainer _container; public StructureMapServiceProvider(IContainer container, bool scoped = false) { _container = container; } public object GetService(Type type) { try { return _container.GetInstance(type); } catch { return null; } } }
مورد دوم کلاس StructureMapServiceScope میباشد:
internal class StructureMapServiceScope : IServiceScope { private readonly IContainer _container; private readonly IContainer _childContainer; private IServiceProvider _provider; public StructureMapServiceScope(IContainer container) { _container = container; _childContainer = _container.GetNestedContainer(); _provider = new StructureMapServiceProvider(_childContainer, true); } public IServiceProvider ServiceProvider => _provider; public void Dispose() { _provider = null; if (_childContainer != null) _childContainer.Dispose(); } }
مورد سوم StructureMapServiceScopeFactory میباشد:
internal class StructureMapServiceScopeFactory : IServiceScopeFactory { private IContainer _container; public StructureMapServiceScopeFactory(IContainer container) { _container = container; } public IServiceScope CreateScope() { return new StructureMapServiceScope(_container); } }
مورد بعدی کلاس StructureMapPopulator میباشد. وظیفه این کلاس جمع آوری اطلاعات مربوط به سرویسها میباشد.
internal class StructureMapPopulator { private IContainer _container; public StructureMapPopulator(IContainer container) { _container = container; } public void Populate(IEnumerable<ServiceDescriptor> descriptors) { _container.Configure(c => { c.For<IServiceProvider>().Use(new StructureMapServiceProvider(_container)); c.For<IServiceScopeFactory>().Use<StructureMapServiceScopeFactory>(); foreach (var descriptor in descriptors) { switch (descriptor.Lifetime) { case ServiceLifetime.Singleton: Use(c.For(descriptor.ServiceType).Singleton(), descriptor); break; case ServiceLifetime.Transient: Use(c.For(descriptor.ServiceType), descriptor); break; case ServiceLifetime.Scoped: Use(c.For(descriptor.ServiceType), descriptor); break; } } }); } private static void Use(GenericFamilyExpression expression, ServiceDescriptor descriptor) { if (descriptor.ImplementationFactory != null) { expression.Use(Guid.NewGuid().ToString(), context => { return descriptor.ImplementationFactory(context.GetInstance<IServiceProvider>()); }); } else if (descriptor.ImplementationInstance != null) { expression.Use(descriptor.ImplementationInstance); } else if (descriptor.ImplementationType != null) { expression.Use(descriptor.ImplementationType); } else { throw new InvalidOperationException("IServiceDescriptor is invalid"); } } }
و در آخر کلاس StructureMapRegistration میباشد:
public static class StructureMapRegistration { public static void Populate(this IContainer container, IEnumerable<ServiceDescriptor> descriptors) { var populator = new StructureMapPopulator(container); populator.Populate(descriptors); } }
نهایتاً باید متد ConfigurationServices در کلاس StartUp را اندکی تغییر دهیم.
public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc(); var container = new Container(); container.Configure(configure => { configure.For<ICoreService>().Use<CoreServise>(); }); container.Populate(services); return container.GetInstance<IServiceProvider>(); }
در کد بالا، متد ConfigurationServices به جای آنکه Void برگرداند، نمونهای از اینترفیس IServiceProvider را برمیگرداند. حال اگر برنامه را اجرا کنیم، وابستگیها توسط StructureMap تزریق شده و برنامه بدون هیچ مشکلی اجرا میشود.