private static string GetScript() { string path = AppDomain.CurrentDomain.BaseDirectory + @"Scripts\script.sql"; var file = new FileInfo(path); string script = file.OpenText().ReadToEnd(); return script; } private static void ExecuteScript() { string script = GetScript(); //split the script on "GO" commands var splitter = new[] {"\r\nGO\r\n"}; string[] commandTexts = script.Split(splitter, StringSplitOptions.RemoveEmptyEntries); foreach (string commandText in commandTexts) { using (var ctx = new DbContext()) { if (!string.IsNullOrEmpty(commandText)) { ctx.Database.ExecuteSqlCommand(commandText); } } } }
Type Safety
string text = “String value”; int textLength = text.Length; int textMonth = text.Month; // won’t compile
public interface IGeometricShape { double Circumference { get; } double Area { get; } } public class Square : IGeometricShape { public double Side { get; set; } public double Circumference => 4 * Side; public double Area => Side * Side; } public class Circle : IGeometricShape { public double Radius { get; set; } public double Circumference => 2 * Math.PI * Radius; public double Area => Math.PI * Radius * Radius; } IGeometricShape circle = new Circle { Radius = 1 }; Square square = ((Square)circle); // no compiler error var side = square.Side;
Dynamic Binding
dynamic text = “String value”; int textLength = text.Length; int textMonth = text.Month; // throws exception at runtime
public dynamic GetAnonymousType() { return new { Name = “John”, Surname = “Doe”, Age = 42 }; } dynamic value = GetAnonymousType(); Console.WriteLine($”{value.Name} {value.Surname}, {value.Age}”);
string json = @" { ""name"": ""John"", ""surname"": ""Doe"", ""age"": 42 }"; dynamic value = JObject.Parse(json); Console.WriteLine($"{ value.name} { value.surname}, { value.age}");
ExpandoObject
dynamic person = new ExpandoObject(); person.Name = "John"; person.Surname = "Doe"; person.Age = 42; person.ToString = (Func<string>)(() => $”{person.Name} {person.Surname}, {person. Age}”); Console.WriteLine($"{ person.Name}{ person.Surname}, { person.Age}");
برای اینکه ببینیم در زمان اجرا چه اعضایی به این شی اضافه شده، میتوان نمونه ساخته شده از آن را به نوع <IDictionary<string, object تبدیل و در یک حلقه به آنها دسترسی پیدا کرد. از همین طریق هم میشود عضوی را حذف کرد.
var dictionary = (IDictionary<string, object>)person; foreach (var member in dictionary) { Console.WriteLine($”{member.Key} = {member.Value}”); } dictionary.Remove(“ToString”);
DynamicObject
از آنجایی که ExpandoObject برای سناریوهای ساده کاربرد دارد و کنترل کمتری بر روی اعضا و نمونههای ایجاد شدهی توسط آن داریم، میتوان از شیء DynamicObject استفاده کرد؛ البته نیاز به کدنویسی بیشتری دارد. پیادهسازی اعضا برای شیء DynamicObject در یک کلاس صورت میگیرد که در زیر آورده شدهاست:
class MyDynamicObject : DynamicObject { private readonly Dictionary<string, object> members = new Dictionary<string, object>(); public override bool TryGetMember(GetMemberBinder binder, out object result) { if (members.ContainsKey(binder.Name)) { result = members[binder.Name]; return true; } else { result = null; return false; } } public override bool TrySetMember(SetMemberBinder binder, object value) { members[binder.Name] = value; return true; } public bool RemoveMember(string name) { return members.Remove(name); } } dynamic person = new MyDynamicObject(); person.Name = “John”; person.Surname = “Doe”; person.Age = 42; person.AsString = (Func<string>)(() => $”{person.Name} {person.Surname}, {person. Age}”);
مشاهدهی جزئیات اطلاعات سرور و بستههای نصب شدهی بر روی آن
در نگارشهای قبل از RTM، با فراخوانی app.UseRuntimeInfoPage در متد Configure کلاس Startup، ریز اطلاعاتی از وضعیت سرور و بستههای موجود در آن با مراجعهی به آدرس http://site/runtimeinfo نمایش داده میشدند. این مورد خاص از نگارش RTM حذف شدهاست (احتمالا به دلایل امنیتی). البته اگر علاقمند به بررسی کدهای آن باشید، هنوز تاریخچهی آن در GitHub موجود است .
مدیریت خطاها در برنامههای ASP.NET Core 1.0
به متد Configure کلاس Startup مراجعه کرد و یک سطر استثناء را به ابتدای کدهای Middleware انتهایی آن اضافه کنید:
public void Configure(IApplicationBuilder app) { app.Run(async context => { throw new Exception("Generic Error"); await context.Response.WriteAsync("Hello DNT!"); }); }
در این حالت اگر برنامه را اجرا کنیم، این خروجی را دریافت خواهیم کرد:
و اگر به وضعیت بازگشت داده شدهی از طرف سرور دقت کنیم، فقط internal server error است:
در اینجا برخلاف نگارشهای قبلی ASP.NET، دیگر حتی صفحهی زرد رنگ معروف نمایش خطاها (yellow screen of death) نیز فعال نیستند. برای فعال سازی آن نیاز است Middleware مرتبط با آنرا به نحو ذیل به برنامه معرفی کنیم:
public void Configure(IApplicationBuilder app) { app.UseDeveloperExceptionPage();
به دلایل امنیتی و عدم نشت اطلاعات سمت سرور و خصوصا عدم امکان دیباگ از راه دور برنامه توسط مهاجمین، این Middleware به صورت پیش فرض فعال نیست.
بنابراین این سؤال مطرح میشود که چگونه میتوان این صفحه را تنها در حین توسعهی برنامه نمایش داد؟
پاسخ آن به نحوهی طراحی متد Configure در کلاس Startup بر میگردد. این متد امضای ثابتی را ندارد. هر تعداد سرویسی را که نیاز داشتید، میتوانید به عنوان پارامتر این متد معرفی کنید و کار تزریق وابستگیها و نمونه سازی آنها، توسط امکانات توکار ASP.NET Core به صورت خودکار انجام میشود. برای مثال سرویس IApplicationBuilder، یکی از سرویسهای توکار ASP.NET Core است و برای تنظیم آن نیازی نیست تا کار خاصی را انجام دهیم. به همین جهت است که صرفا معرفی اینترفیس آن در این متد، وهلهای را از سازندهی برنامه در اختیار ما قرار میدهد. سرویسها را در مطلبی جداگانه مورد بررسی قرار خواهیم داد، اما فعلا جهت تکمیل بحث باید درنظر داشت که یکی دیگر از سرویسهای توکار ASP.NET Core، به نام IHostingEnvironment، اطلاعاتی را در مورد محیطی که برنامه را در آن اجرا میکنیم در اختیار ما قرار میدهد:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); }
این متغیر محیطی میتواند سه مقدار Development, Staging و Production را داشته باشد و بر اساس این متغیر و مقدار آن است که یکی از سه متد ذیل مفهوم پیدا میکنند و true یا false را باز میگردانند:
if(env.IsDevelopment()){ } if(env.IsProduction()){ } if(env.IsStaging()){ }
نمایش و مدیریت خطاها در حالت Production
از app.UseDeveloperExceptionPage صرفا در حالت توسعه استفاده کنید؛ چون اطلاعات نمایش داده شدهی توسط آن، بیش از اندازه برای مهاجمین مفید است. اما در حالت توزیع نهایی بر روی سرور چه باید کرد؟
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler(errorHandlingPath: "/MyControllerName/SomeActionMethodName"); }
به علاوه در اینجا (در این قسمت خاص برنامه که توسط پارامتر errorHandlingPath مشخص شدهاست) با استفاده از قطعه کد ذیل، دسترسی کاملی را به اطلاعات خطای رخ داده، جهت ثبت و لاگ آن دارید:
var feature = HttpContext.Features.Get<IExceptionHandlerFeature>(); var error = feature?.Error;
بررسی میانافزار StatusCode
این میان افزار برای مدیریت responseهایی که status code آنها بین 400 تا 600 هستند، طراحی شدهاست. بر اساس این شمارهها، میتوان خطای خاصی را بازگشت داده و یا کاربر را به یک صفحه یا کنترلر خاصی در برنامه، هدایت کرد.
در حالت عادی ثبت آن
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseStatusCodePages(); app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler(errorHandlingPath: "/MyControllerName/SomeActionMethodName"); } }
برای نمونه در اینجا مسیری درخواست داده شدهاست که توسط برنامه پردازش نمیشود و وجود ندارد.
اگر خواستید تا status code واقعی، به کاربر بازگشت داده شود، اما خروجی نمایش داده شده را سفارشی سازی کنید، میتوانید از متد UseStatusCodePagesWithReExecute استفاده نمائید:
app.UseStatusCodePagesWithReExecute("/MyControllerName/SomeActionMethodName/{0}");
در فریمورک NET. ابزارهای مختلفی برای کار با دادههای XML در نظر گرفته شدهاست که بعد از نسخه 3.5 آن، انتخاب اول LINQ to XML می باشد. در این مطلب قصد داریم API ای را برای خواندن اطلاعات فایلهای XML با استفاده از LINQ to XML و انقیاد پویا در سیشارپ (Dynamic Binding) تهیه کنیم.
راه حل اول: استفاده از ExpandoObject
public static class ExpandoXml { public static dynamic AsExpando(this XDocument document) { return CreateExpando(document.Root); } private static dynamic CreateExpando(XElement element) { var result = new ExpandoObject() as IDictionary<string, object>; if (element.Elements().Any(e => e.HasElements)) { var list = new List<ExpandoObject>(); result.Add(element.Name.ToString(), list); foreach (var childElement in element.Elements()) { list.Add(CreateExpando(childElement)); } } else { foreach (var leafElement in element.Elements()) { result.Add(leafElement.Name.ToString(), leafElement.Value); } } return result; } }
در تکه کد بالا از طریق متد CreateExpando به صورت بازگشتی ابتدا بررسی میشود که آیا عنصر جاری دارای عناصری میباشد و همچنین آیا آنها دارای فرزند میباشند یا خیر؛ در صورت برقراری شرط، نتیجهی اجرای متد CreateExpando بر روی تک تک عناصر فرزند را درون لیستی از ExpandoObject قرار داده و سپس آن لیست نیز به عنوان Value عنصر جاری در نظر گرفته میشود. در صورت عدم برقراری شرط مذکور، مقادیر مربوط به عناصر فرزند را در قالب یک ExpandoObject به عنوان خروجی بازگشت خواهد داد.
راه حل دوم: استفاده از DynamicObject
public class DynamicXml : DynamicObject, IEnumerable { private readonly dynamic _xml; public DynamicXml(string fileName) { _xml = XDocument.Load(fileName); } public DynamicXml(dynamic xml) { _xml = xml; } public IEnumerator GetEnumerator() { foreach (var item in _xml.Elements()) { yield return new DynamicXml(item); } } public override bool TryGetMember(GetMemberBinder binder, out object result) { var xml = _xml.Element(binder.Name); if (xml != null) { result = new DynamicXml(xml); return true; } var attribute = _xml.Attribute(binder.Name); if (attribute != null) { result = new DynamicXml(attribute); return true; } result = null; return false; } public static implicit operator string(DynamicXml xml) { return xml._xml.Value; } }
کلاس DynamicXml از طریق سازنده اول، نام فایل را دریافت کرده و از طریق LINQ to XML با استفاده از متد Load کلاس XDocument، فایل مورد نظر بارگذاری شده و درون فیلدی به نام xml_ از نوع dynamic نگه داشته میشود. کار بعدی، بازنویسی متد TryGetMember میباشد. در بدنه بازنویسی شده این متد ابتدا بررسی میشود که آیا با نام خصوصت درخواست شده عنصری در داده XML وجود دارد یا خیر؛ در صورت موجود بودن، پارامتر result با یک وهله جدید از DynamicXml مقدار دهی میشود که عنصر یافت شده از طریق سازنده دوم، به عنوان داده xml برای مقدار دهی فیلد xml_ به عنوان آرگومان ارسال میشود. در صورت عدم وجود عنصر مذکور، بدنبال خصوصیتی با آن نام بوده و در صورت یافت شدن، باز به عنوان یک وهله DynamicXml برای مقدار دهی result استفاده میشود.
در ادامه برای نسبت دادن یک وهله از DynamicXml به یک متغیر string و دستیابی به مقدار یک عنصر که از طریق خصوصیت، درخواست میشود نیاز است تا اپراتور ضمنی string را نیز برای کلاس بالا نظر بگیریم. همچنین برای ایجاد امکان پیمایش برروی عناصر فرزند از طریق foreach، لازم است واسط IEnumerable را نیز پیاده سازی کرده باشیم.
طریقه استفاده
class Program { static void Main(string[] args) { var doc1 = XDocument.Load("Employees.xml"); foreach (var element in doc1.Element("Employees").Elements("Employee")) { Console.WriteLine(element.Element("FirstName").Value); } dynamic doc2 = XDocument.Load("Employees.xml").AsExpando(); foreach (var employee in doc2.Employees) { Console.WriteLine(employee.FirstName); } dynamic doc3 = new DynamicXml("Employees.xml"); foreach (var employee in doc3.Employees) { Console.WriteLine(employee.FirstName); Console.WriteLine(employee.Id); } } }
<?xml version="1.0" encoding="utf-8" ?> <Employees> <Employee Id="1"> <FirstName> Employee1 </FirstName> </Employee> <Employee Id="2"> <FirstName> Employee2 </FirstName> </Employee> <Employee Id="3"> <FirstName> Employee3 </FirstName> </Employee> <Employee Id="4"> <FirstName> Employee4 </FirstName> </Employee> </Employees>
نام این جدول را با درنظر گرفتن شرایط موجود میتوان Resources گذاشت.
ستون Name برای ذخیره نام منبع درنظر گرفته شده است. این نام برابر نام منابع درخواستی در سیستم مدیریت منابع ASP.NET است که درواقع برابر همان نام فایل منبع اما بدون پسوند resx. است.
ستون Key برای نگهداری کلید ورودی منبع استفاده میشود که دقیقا برابر همان مقداری است که درون فایلهای resx. ذخیره میشود.
ستون Culture برای ذخیره کالچر ورودی منبع به کار میرود. این مقدار میتواند برای کالچر پیشفرض برنامه برابر رشته خالی باشد.
ستون Value نیز برای نگهداری مقدار ورودی منبع استفاده میشود.
برای ستون Id میتوان از GUID نیز استفاده کرد. در اینجا برای راحتی کار از نوع داده bigint و خاصیت Identity برای تولید خودکار آن در Sql Server استفاده شده است.
نکته: برای امنیت بیشتر میتوان یک Unique Constraint بر روی سه فیلد Name و Key و Culture اعمال کرد.
برای نمونه به تصویر زیر که ذخیره تعدای ورودی منبع را درون جدول Resources نمایش میدهد دقت کنید:
اصلاح کلاس DbResourceProviderFactory
برای ذخیره منابع محلی، جهت اطمینان از یکسان بودن نام منبع، متد مربوطه در کلاس DbResourceProviderFactory باید بهصورت زیر تغییر کند:
public override IResourceProvider CreateLocalResourceProvider(string virtualPath) { if (!string.IsNullOrEmpty(virtualPath)) { virtualPath = virtualPath.Remove(0, virtualPath.IndexOf('/') + 1); // removes everything from start to the first '/' } return new LocalDbResourceProvider(virtualPath); }
ارتباط با دیتابیس
خوشبختانه برای تبادل اطلاعات با جدول بالا امروزه راههای زیادی وجود دارد. برای پیادهسازی آن مثلا میتوان از یک اینترفیس استفاده کرد. سپس با استفاده از سازوکارهای موجود مثلا بهکارگیری IoC، نمونه مناسبی از پیادهسازی اینترفیس مذبور را در اختیار برنامه قرار داد.
اما برای جلوگیری از پیچیدگی بیش از حد و دور شدن از مبحث اصلی، برای پیادهسازی فعلی از EF Code First به صورت مستقیم در پروژه استفاده شده است که سری آموزشی کاملی از آن در همین سایت وجود دارد.
پس از پیادهسازی کلاسهای مرتبط برای استفاده از EF Code First، از کلاس ResourceData که در بخش اول نیز نشان داده شده بود، برای کپسوله کردن ارتباط با دادهها استفاده میشود که نمونهای ابتدایی از آن در زیر آورده شده است:
using System.Collections.Generic; using System.Linq; using DbResourceProvider.Models; namespace DbResourceProvider.Data { public class ResourceData { private readonly string _resourceName; public ResourceData(string resourceName) { _resourceName = resourceName; } public Resource GetResource(string resourceKey, string culture) { using (var data = new TestContext()) { return data.Resources.SingleOrDefault(r => r.Name == _resourceName && r.Key == resourceKey && r.Culture == culture); } } public List<Resource> GetResources(string culture) { using (var data = new TestContext()) { return data.Resources.Where(r => r.Name == _resourceName && r.Culture == culture).ToList(); } } } }
using System.Collections.Generic; using System.Globalization; using DbResourceProvider.Data; namespace DbResourceProvider { public class DbResourceManager { private readonly string _resourceName; private readonly Dictionary<string, Dictionary<string, object>> _resourceCacheByCulture; public DbResourceManager(string resourceName) { _resourceName = resourceName; _resourceCacheByCulture = new Dictionary<string, Dictionary<string, object>>(); } public object GetObject(string resourceKey, CultureInfo culture) { return GetCachedObject(resourceKey, culture.Name); } private object GetCachedObject(string resourceKey, string cultureName) { if (!_resourceCacheByCulture.ContainsKey(cultureName)) _resourceCacheByCulture.Add(cultureName, new Dictionary<string, object>()); var cachedResource = _resourceCacheByCulture[cultureName]; lock (this) { if (!cachedResource.ContainsKey(resourceKey)) { var data = new ResourceData(_resourceName); var dbResource = data.GetResource(resourceKey, cultureName); if (dbResource == null) return null; var cachedResources = _resourceCacheByCulture[cultureName]; cachedResources.Add(dbResource.Key, dbResource.Value); } } return cachedResource[resourceKey]; } } }
private object GetCachedObject(string resourceKey, string cultureName) { lock (this) { if (!_resourceCacheByCulture.ContainsKey(cultureName)) { _resourceCacheByCulture.Add(cultureName, new Dictionary<string, object>()); var cachedResources = _resourceCacheByCulture[cultureName]; var data = new ResourceData(_resourceName); var dbResources = data.GetResources(cultureName); foreach (var dbResource in dbResources) { cachedResources.Add(dbResource.Key, dbResource.Value); } } } var cachedResource = _resourceCacheByCulture[cultureName]; return !cachedResource.ContainsKey(resourceKey) ? null : cachedResource[resourceKey]; }
using System.Collections; using System.Collections.Generic; using System.Globalization; namespace DbResourceProvider { public class CultureFallbackProvider : IEnumerable<CultureInfo> { private readonly CultureInfo _startingCulture; private readonly CultureInfo _neutralCulture; private readonly bool _tryParentCulture; public CultureFallbackProvider(CultureInfo startingCulture = null, CultureInfo neutralCulture = null, bool tryParentCulture = true) { _startingCulture = startingCulture ?? CultureInfo.CurrentUICulture; _neutralCulture = neutralCulture; _tryParentCulture = tryParentCulture; } #region Implementation of IEnumerable<CultureInfo> public IEnumerator<CultureInfo> GetEnumerator() { var reachedNeutralCulture = false; var currentCulture = _startingCulture; do { if (_neutralCulture != null && currentCulture.Name == _neutralCulture.Name) { yield return CultureInfo.InvariantCulture; reachedNeutralCulture = true; break; } yield return currentCulture; currentCulture = currentCulture.Parent; } while (_tryParentCulture && !HasInvariantCultureName(currentCulture)); if (!_tryParentCulture || HasInvariantCultureName(_startingCulture) || reachedNeutralCulture) yield break; yield return CultureInfo.InvariantCulture; } #endregion #region Implementation of IEnumerable IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion private bool HasInvariantCultureName(CultureInfo culture) { return culture.Name == CultureInfo.InvariantCulture.Name; } } }
public object GetObject(string resourceKey, CultureInfo culture) { foreach (var currentCulture in new CultureFallbackProvider(culture)) { var value = GetCachedObject(resourceKey, currentCulture.Name); if (value != null) return value; } throw new KeyNotFoundException("The specified 'resourceKey' not found."); }
ابتدا تنظیمات موردنیاز فایل کانفیگ را که در قسمت قبل نشان داده شد، در برنامه خود اعمال کنید.
دادههای نمونه نشان داده شده در ابتدای این مطلب را درنظر بگیرید. حال اگر در یک برنامه وب اپلیکیشن، صفحه Default.aspx در ریشه سایت حاوی دو کنترل زیر باشد:
<asp:Label ID="Label1" runat="server" meta:resourcekey="Label1" /> <asp:Label ID="Label2" runat="server" meta:resourcekey="Label2" />
سپس تغییر زیر را در فایل web.config اعمال کنید تا کالچر UI سایت به fa تغییر یابد (به بخش "uiCulture="fa دقت کنید):
<globalization uiCulture="fa" resourceProviderFactoryType = "DbResourceProvider.DbResourceProviderFactory, DbResourceProvider" />
میبینید که با توجه به عدم وجود مقداری برای Label2.Text برای کالچر fa، عملیات fallback اتفاق افتاده است.
کار تولید یک پرووایدر منابع سفارشی دیتابیسی به اتمام رسید. تا اینجا اصول کلی تولید یک پرووایدر سفارشی شرح داده شد. بدین ترتیب میتوان برای هر حالت خاص دیگری نیز پرووایدرهای سفارشی مخصوص ساخت تا مدیریت منابع به آسانی تحت کنترل برنامه نویس قرار گیرد.
اما نکتهای را که باید به آن توجه کنید این است که در پیادهسازیهای نشان داده شده با توجه به نحوه کششدن مقادیر ورودیها، اگر این مقادیر در دیتابیس تغییر کنند، تا زمانیکه سایت ریست نشود این تغییرات در برنامه اعمال نخواهد شد. زیرا همانطور که اشاره شد، مدیریت نمونههای تولیدشده از پرووایدرهای منابع برای هر منبع درخواستی درنهایت برعهده ASP.NET است. بنابراین باید مکانیزمی پیاده شود تا کلاس DbResourceManager از بهروزرسانی ورودیهای کششده اطلاع یابد تا آنها را ریفرش کند.
در ادامه درباره روشهای مختلف نحوه پیادهسازی قابلیت بهروزرسانی ورودیهای منابع در زمان اجرا با استفاده از پرووایدرهای منابع سفارشی بحث خواهد شد. همچنین راهحلهای مختلف استفاده از این پرووایدرهای سفارشی در جاهای مختلف پروژههای MVC شرح داده میشود.
البته مباحث پیشرفتهتری چون تزریق وابستگی برای پیادهسازی لایه ارتباط با دیتابیس در بیرون و یا تولید یک Factory برای تزریق کامل پرووایدر منابع از بیرون نیز جای بحث و بررسی دارد.
منابع
http://msdn.microsoft.com/en-us/library/aa905797.aspx
http://msdn.microsoft.com/en-us/library/system.web.compilation.resourceproviderfactory.aspx
http://www.dotnetframework.org/default.aspx/.../ResourceFallbackManager@cs
http://www.codeproject.com/Articles/14190/ASP-NET-2-0-Custom-SQL-Server-ResourceProvider
var d = new Date(); $.jCookies({ name: 'dotnettips.info', value: { Title: 'ساخت کوکی با jcookie', Author: 'علی یگانه مقدم', Seen: d.getDate(), Favorite: true } });
$.jCookies ({ name : 'User', value : { username : 'Bob' , level : 5 }, minutes : 60 });
$.jCookies.defaults = { name : '', value : '', days : 27 }
var values = $.jCookies ({ get: 'dotnettips.info' });
var values = $.jCookies({ get: 'Rutabaga', error: true });
Error : { arguments : undefined, message : "Invalid base64 data", stack : "—", type : undefined }
var values = $.jCookies({ get: '*' }); alert(values["dotnettips.info"].Title); alert(values["data2"].Title);
var value = $.jCookies({ erase: 'dotnettips.info' });
- برگشت دادهها از حالت رمزگذاری base64
- دادهها در فرمت json هستند و باید به حالتی قابل استفاده در محیط شی گرا تبدیل شوند.
Title | ایجاد کوکی با jcookie |
Author | علی یگانه مقدم |
Seen | 2015/1/14 |
Favorite | true |
byte[] from64 = Convert.FromBase64String(Page.Request.Cookies["dotnettips.info"].Value); string json = Encoding.UTF8.GetString(from64); Dictionary<string, object> article =new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(json); Page.Response.Write("Title: "+ (string)article["Title"]);
byte[] from64 = Convert.FromBase64String(Page.Request.Cookies["dotnettips.info"].Value); string json = Encoding.Unicode.GetString(from64); Dictionary<string, object> article =new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(json); Page.Response.Write("Title: "+ (string)article["Title"]);
آماده سازی برنامههای دات نت برای دسترسی به API مترو ویندوز 8.1
ابتدا یک برنامهی کنسول دات نت 4.5.1 را آغاز کنید. برای دسترسی به API ویندوز 8.1 حتما نیاز است که حداقل از دات نت 4.5.1 شروع کرد. سپس برنامه را در VS.NET بسته و فایل پروژه آنرا در یک ادیتور متنی باز کنید.
در ابتدای فایل csproj، نیاز است سطر TargetPlatformVersion ذیل اضافه شود.
<PropertyGroup> <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion> <TargetPlatformVersion>8.1</TargetPlatformVersion> </PropertyGroup>
<ItemGroup> <Reference Include="System" /> <Reference Include="System.ComponentModel.DataAnnotations" /> <Reference Include="System.Core" /> <Reference Include="System.ObjectModel" /> <Reference Include="System.Xml.Linq" /> <Reference Include="System.Data.DataSetExtensions" /> <Reference Include="Microsoft.CSharp" /> <Reference Include="System.Data" /> <Reference Include="System.Xml" /> <Reference Include="System.Threading" /> <Reference Include="System.Threading.Tasks" /> </ItemGroup> <ItemGroup> <Reference Include="Windows" /> <Reference Include="System.Runtime" /> <Reference Include="System.Runtime.WindowsRuntime" /> </ItemGroup>
یک نکته
اگر میخواهید این فرآیند را ساده و خودکار کنید، از قالبهای پروژهی مخصوص DesktopWinRT.Templates.vsix استفاده نمائید.
DesktopWinRT.Templates.vsix
افزودن ارجاعی به Nito.AsyncEx
چون برنامهی مورد استفاده کنسول است و API ویندوز 8 کاملا async طراحی شدهاست، نیاز است با کمک AsyncContext موجود در کتابخانهی Nito.AsyncEx بتوان از امکانات async و await در متد Main برنامه استفاده کرد. البته اگر از سایر برنامههای دسکتاپ استفاده میکنید، فقط کافی است امضای متد رخدادن گردان را به async تغییر دهید.
install-package Nito.AsyncEx
تبدیل استریمهای دات نت به استریمهای WinRT
اکثر متدهای WinRT با استریمهایی از نوع IRandomAccessStream کار میکنند. برای اینکه بتوان استریم استاندارد دات نت را به این نوع تبدیل کرد، میتوان از کلاسهای ذیل کمک گرفت:
using System; using System.IO; using Windows.Storage.Streams; namespace ConsoleWin81PdfApiTest { public static class MicrosoftStreamExtensions { public static IRandomAccessStream AsRandomAccessStream(this Stream stream) { return new RandomStream(stream); } } class RandomStream : IRandomAccessStream { readonly Stream _internstream; public RandomStream(Stream underlyingstream) { _internstream = underlyingstream; } public IInputStream GetInputStreamAt(ulong position) { _internstream.Position = (long)position; return _internstream.AsInputStream(); } public IOutputStream GetOutputStreamAt(ulong position) { _internstream.Position = (long)position; return _internstream.AsOutputStream(); } public ulong Size { get { return (ulong)_internstream.Length; } set { _internstream.SetLength((long)value); } } public bool CanRead { get { return _internstream.CanRead; } } public bool CanWrite { get { return _internstream.CanWrite; } } public IRandomAccessStream CloneStream() { throw new NotSupportedException(); } public ulong Position { get { return (ulong)_internstream.Position; } } public void Seek(ulong position) { _internstream.Seek((long)position, SeekOrigin.Begin); } public void Dispose() { _internstream.Dispose(); } public Windows.Foundation.IAsyncOperationWithProgress<IBuffer, uint> ReadAsync(IBuffer buffer, uint count, InputStreamOptions options) { return GetInputStreamAt(Position).ReadAsync(buffer, count, options); } public Windows.Foundation.IAsyncOperation<bool> FlushAsync() { return GetOutputStreamAt(Position).FlushAsync(); } public Windows.Foundation.IAsyncOperationWithProgress<uint, uint> WriteAsync(IBuffer buffer) { return GetOutputStreamAt(Position).WriteAsync(buffer); } } }
خواندن فایلهای PDF و تبدیل صفحات آنها به تصویر
در ادامه کد کامل استفاده از API جدید ویندوز 8.1 را جهت خواندن فایلهای PDF ملاحظه میکنید. این امکانات جدید در فضای نام Windows.Data.Pdf قرار دارند و صرفا امکان خواندن فایلهای PDF را تدارک دیدهاند.
using System; using System.IO; using System.Threading.Tasks; using Windows.Data.Pdf; using Nito.AsyncEx; namespace ConsoleWin81PdfApiTest { class Program { static void Main(string[] args) { AsyncContext.Run(async () => { await test(); }); } private static async Task test() { using (var randomAccessStream = File.Open("PieChartPdfReport.pdf", FileMode.Open).AsRandomAccessStream()) { var pdfDocument = await PdfDocument.LoadFromStreamAsync(randomAccessStream); for (uint i = 0; i < pdfDocument.PageCount; i++) { using (var page = pdfDocument.GetPage(i)) { /*var renderOptions = new PdfPageRenderOptions { BackgroundColor = Colors.LightGray, DestinationHeight = (uint) (page.Size.Height*10) };*/ using (var stream = File.Open(string.Format("page-{0}.png", i + 1), FileMode.OpenOrCreate).AsRandomAccessStream()) { await page.RenderToStreamAsync(stream/*, renderOptions*/); await stream.FlushAsync(); } } } } } } }
توضیحات:
- متد AsyncContext.Run جزو امکانات Nito.AsyncEx است و امکان نوشتن کدهای await دار را در متد Main یک برنامهی کنسول فراهم میکند.
- متد File.Openدات نت، خروجی از نوع استریم دارد. برای تبدیل آن به نوع IRandomAccessStream، از متد الحاقی AsRandomAccessStream که پیشتر تهیه کردیم، میتوان استفاده کرد.
- در ادامه متد PdfDocument.LoadFromStreamAsync این استریم خاص را دریافت کرده و امکان دسترسی به API ویندوز 8.1 را میسر میکند.
- توسط متد pdfDocument.GetPage میتوان به صفحات مختلف فایل PDF باز شده دسترسی یافت. در اینجا متد page.RenderToStreamAsync، سبب رندر شدن صفحه با فرمت PNG میشود. این خروجی نهایتا باید در یک استریم از نوع IRandomAccessStream ثبت شود. در اینجا نیز میتوان از متد File.Open در حالت FileMode.OpenOrCreate استفاده کرد.
- اگر میخواهید ابعاد تصویر نهایی و ویژگیهای آنرا تغییر دهید، میتوان از پارامتر دوم متد page.RenderToStreamAsync استفاده کرد که شیءایی از نوع PdfPageRenderOptions را میپذیرد.
کدهای کامل این پروژه را از اینجا میتوانید دریافت کنید
MicrosoftStreamExtensions.zip
برای مطالعه بیشتر
How to use specific WinRT API from Desktop apps
How to call WinRT APIs from .NET desktop apps
ObsoleteAttribute
ObsoleteAttribute بر روی تمامی عناصر یک برنامه بجز assemblies, modules، پارامترها و مقادیر بازگشتی قابل استفاده است. علامتگذاری یک عنصر به عنوان منسوخ شده، به کاربر استفاده کننده اطلاع میدهد که این عنصر در نسخههای آینده حذف خواهد شد.
با استفاده از پروپرتی Message آن پیامی را به کاربر استفاده کننده نشان خواهد داد و توصیه میشود در این پیام یک راه حل نیز ارائه شود.
پروپرتی IsError در صورتی که مقدار آن به true تعیین شده باشد و کامپایلر در صورتی که عنصری که این خصوصیت بر روی آن تعریف شده است، استفاده شده باشد، در پنجره Error List، پیام مربوط به Obsolete را نشان میدهد. برای مثال پس از استفاده از کلاس زیر، OrderDetailTotal به صورت warning و CalculateOrderDetailTotal به صورت Error در پنجره Error List نشان داده میشود.
public static class ObsoleteExample { // Mark OrderDetailTotal As Obsolete. [ObsoleteAttribute("This property (OrderDetailTotal) is obsolete. Use InvoiceTotal instead.", false)] public static decimal OrderDetailTotal { get { return 12m; } } public static decimal InvoiceTotal { get { return 25m; } } // Mark CalculateOrderDetailTotal As Obsolete. [ObsoleteAttribute("This method is obsolete. Call CalculateInvoiceTotal instead.", true)] public static decimal CalculateOrderDetailTotal() { return 0m; } public static decimal CalculateInvoiceTotal() { return 1m; } }
DefaultValueAttribute
DefaultValueAttribute جهت تعیین مقدار پیش فرض یک پروپرتی استفاده میشود. شما میتوانید یک DefaultValueAttribute را با هر مقداری ایجاد کنید. ایجاد مقدار پیش فرض برای یک پروپرتی باعث نمیشود که مقداردهی اولیهای به آن انجام گیرد؛ برای این کار نیاز به کدنویسی میباشد.
مثال زیر نحوه استفاده و مقداردهی اولیه پروپرتیها را نشان میدهد.
public class DefaultValueAttributeTest { public DefaultValueAttributeTest() { // Use the DefaultValue propety of each property to actually set it, via reflection. foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(this)) { var attr = prop.Attributes[typeof(DefaultValueAttribute)] as DefaultValueAttribute; if (attr != null) prop.SetValue(this, attr.Value); } } [DefaultValue(28)] public int Age { get; set; } [DefaultValue("Vahid")] public string FirstName { get; set; } [DefaultValue("Mohammad Taheri")] public string LastName { get; set; } public override string ToString() { return $"{this.FirstName} {this.LastName} is {this.Age}."; } }
DebuggerBrowsableAttribute
در صورت استفاده از DebuggerBrowsableAttribute ، شما میتوانید نحوه نمایش یک عضو را در پنجره متغیرها، در زمان دیباگ، تعیین کنید.public class DebuggerBrowsableTest { [DebuggerBrowsable(DebuggerBrowsableState.Never)] // عدم نمایش در زمان دیباگ در پنجره متغیرها public string FirstName { get; set; } [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] // مقدار پیش فرض public string LastName { get; set; } [DebuggerBrowsable( DebuggerBrowsableState.RootHidden )] // عدم نمایش در زمان دیباگ در پنجره متغیرها public string FullName => FirstName + " " + LastName; [DebuggerBrowsable( DebuggerBrowsableState.RootHidden )] // تنها در زمانی که یک آرایه یا لیست باشد نمایش داده میشود public string[] FullNameArray => new string[] { FirstName + " " + LastName }; }
اگر از کد مثال بالا استفاده کنید و با استفاده از کلید F11 به صورت خط به خط دستورات را اجرا کنید، مشاهده خواهید کرد متغیر FirstName و FullName در پنجره Autos نشان داده نخواهد شد.
Operator ??
عملگر ?? در صورتی که عملوند سمت چپ آن تهی (null) نباشد، مقدار آن را باز میگرداند و در غیر اینصورت مقدار عملوند سمت راست خود را باز میگرداند. نوعهای تهی پذیر (nullable) میتوانند دارای مقدار و یا به صورت تعریف نشده باشند. عملگر ?? وقتی که یک نوع تهی پذیر به یک نوع غیرتهی پذیر انتساب داده میشود، مقدار پیش فرض آن را باز میگرداند.
int? x = null; int y = x ?? -1; Console.WriteLine("y now equals -1 because x was null => {0}", y); int i = DefaultValueOperatorTest.GetNullableInt() ?? default(int); Console.WriteLine("i equals now 0 because GetNullableInt() returned null => {0}", i); string s = DefaultValueOperatorTest.GetStringValue(); Console.WriteLine("Returns 'Unspecified' because s is null => {0}", s ?? "Unspecified");
namespace ShowAlertSignalR.Models { public class Product { public int Id { get; set; } public string Title { get; set; } public string Description { get; set; } public float Price { get; set; } public Category Category { get; set; } } public enum Category { [Display(Name = "دسته بندی اول")] Cat1, [Display(Name = "دسته بندی دوم")] Cat2, [Display(Name = "دسته بندی سوم")] Cat3 } }
namespace ShowAlertSignalR.Models { public class ProductDbContext : DbContext { public ProductDbContext() : base("productSample") { Database.Log = sql => Debug.Write(sql); } public DbSet<Product> Products { get; set; } } }
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Create(Product product) { if (ModelState.IsValid) { db.Products.Add(product); db.SaveChanges(); return RedirectToAction("Index"); } return View(product); }
public ActionResult Index(bool notifyUsers = false) { ViewBag.NotifyUsers = notifyUsers; return View(db.Products.ToList()); }
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Create(Product product) { if (ModelState.IsValid) { db.Products.Add(product); db.SaveChanges(); return RedirectToAction("Index", new { notifyUsers = true }); } return View(product); }
namespace ShowAlertSignalR.Hubs { public class NotificationHub : Hub { public void SendNotification() { Clients.Others.ShowNotification(); } } }
@section scripts { <script src="~/Scripts/jquery.signalR-2.0.2.min.js"></script> <script src="~/signalr/hubs"></script> <script> var notify = $.connection.notificationHub; notify.client.showNotification = function() { $('#result').append("<div class='alert alert-info alert-dismissable'>" + "<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×</button>" + "رکورد جدیدی هم اکنون ثبت گردید، برای مشاهده آن صفحه را بروزرسانی کنید" + "</div>"); }; $.connection.hub.start().done(function() { @{ if (ViewBag.NotifyUsers) { <text>notify.server.sendNotification();</text> } } }); </script> }
سورس مثال جاری : ShowAlertSignalR.zip
در این بین اگر به قالب پیشفرض پروژههای MVC تولید شدهی توسط ASP.NET Core 2.1 نیز دقت کنید، پشتیبانی توکار از پیشنیازهای GDPR در آن لحاظ شدهاست؛ چه از لحاظ گوشزد کردن شرایط حریم خصوصی و پذیرش آن و چه از لحاظ «پاک کردن» و «پس گرفتن» اطلاعات شخصی.
قالب و کوکی پذیرش شرایط حریم خصوصی سایت (Cookie Consent)
اگر قالب پیشفرض یک پروژهی ASP.NET Core 2.1 را اجرا کنید، تصویر فوق را که در آن نوار پذیرش شرایط حریم خصوصی سایت در بالای صفحه درج شدهاست، مشاهده خواهید کرد.
قالب جدید نوار پذیرش شرایط حریم خصوصی در مسیر Views\Shared\_CookieConsentPartial.cshtml واقع شدهاست و در فایل layout برنامه توسط tag helper جدید Partial، رندر و نمایش داده میشود:
<partial name="_CookieConsentPartial" />
@using Microsoft.AspNetCore.Http.Features @{ var consentFeature = Context.Features.Get<ITrackingConsentFeature>(); var showBanner = !consentFeature?.CanTrack ?? false; var cookieString = consentFeature?.CreateConsentCookie(); }
الف) تنظیم نیاز به دریافت پذیرش
public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; });
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // ... app.UseCookiePolicy();
علاوه بر Cookie Consent فوق که در یک قالب ابتدایی MVC نیز درج شدهاست، در قالب پروژههای ASP.NET Core Identity، دو گزینهی جدید دریافت اطلاعات شخصی و همچنین حذف اکانت (دادن حق فراموشی به کاربران) نیز پیشبینی شدهاست: PersonalData.cshtml
البته این صفحه جزو بستهی جدید Microsoft.AspNetCore.Identity.UI است که به همراه ASP.NET Core 2.1 ارائه میشود:
dotnet add package Microsoft.AspNetCore.Identity.UI --version 2.1.0-rc1-final