CA1852: Type can be sealed because it has no subtypes in its containing assembly and is not externally visible
dotnet_diagnostic.CA1852.severity = suggestion
CA1852: Type can be sealed because it has no subtypes in its containing assembly and is not externally visible
dotnet_diagnostic.CA1852.severity = suggestion
[RegularExpression(@"^[\u0600-\u06FF,\u0590-\u05FF,0-9\s]*$", ErrorMessage = "لطفا تنها از اعداد و حروف فارسی استفاده نمائید")] public string FriendlyName { get; set; }
void WriteFile() { using (var doc = new Document(PageSize.LETTER)) { using (var fs = new FileStream("test.pdf", FileMode.Create)) { using (var writer = PdfWriter.GetInstance(doc, fs)) { doc.Open(); var blueFont = FontFactory.GetFont("Arial", 12, Font.NORMAL, BaseColor.BLUE); doc.Add(new Chunk("Go to URL", blueFont).SetAction(new PdfAction("http://www.google.com/", false))); doc.NewPage(); doc.Add(new Chunk("Go to Test", blueFont).SetLocalGoto("entry1")); doc.NewPage(); doc.Add(new Chunk("Test").SetLocalDestination("entry1")); doc.Close(); } } } }
using System; using System.Collections.Generic; using System.IO; using System.Linq; using iTextSharp.text.pdf; namespace ReplaceLinks { public class ReplacePdfLinks { Dictionary<string, PdfObject> _namedDestinations; PdfReader _reader; public string InputPdf { set; get; } public string OutputPdf { set; get; } public Func<Uri, string> UriToNamedDestination { set; get; } public void Start() { updatePdfLinks(); saveChanges(); } private PdfArray getAnnotationsOfCurrentPage(int pageNumber) { var pageDictionary = _reader.GetPageN(pageNumber); var annotations = pageDictionary.GetAsArray(PdfName.ANNOTS); return annotations; } private static bool hasAction(PdfDictionary annotationDictionary) { return annotationDictionary.Get(PdfName.SUBTYPE).Equals(PdfName.LINK); } private static bool isUriAction(PdfDictionary annotationAction) { return annotationAction.Get(PdfName.S).Equals(PdfName.URI); } private void replaceUriWithLocalDestination(PdfDictionary annotationAction) { var uri = annotationAction.Get(PdfName.URI) as PdfString; if (uri == null) return; if (string.IsNullOrWhiteSpace(uri.ToString())) return; var namedDestination = UriToNamedDestination(new Uri(uri.ToString())); if (string.IsNullOrWhiteSpace(namedDestination)) return; PdfObject entry; if (!_namedDestinations.TryGetValue(namedDestination, out entry)) return; annotationAction.Remove(PdfName.S); annotationAction.Remove(PdfName.URI); var newLocalDestination = new PdfArray(); annotationAction.Put(PdfName.S, PdfName.GOTO); var xRef = ((PdfArray)entry).First(x => x is PdfIndirectReference); newLocalDestination.Add(xRef); newLocalDestination.Add(PdfName.FITH); annotationAction.Put(PdfName.D, newLocalDestination); } private void saveChanges() { using (var fileStream = new FileStream(OutputPdf, FileMode.Create, FileAccess.Write, FileShare.None)) using (var stamper = new PdfStamper(_reader, fileStream)) { stamper.Close(); } } private void updatePdfLinks() { _reader = new PdfReader(InputPdf); _namedDestinations = _reader.GetNamedDestinationFromStrings(); var pageCount = _reader.NumberOfPages; for (var i = 1; i <= pageCount; i++) { var annotations = getAnnotationsOfCurrentPage(i); if (annotations == null || !annotations.Any()) continue; foreach (var annotation in annotations.ArrayList) { var annotationDictionary = (PdfDictionary)PdfReader.GetPdfObject(annotation); if (!hasAction(annotationDictionary)) continue; var annotationAction = annotationDictionary.Get(PdfName.A) as PdfDictionary; if (annotationAction == null) continue; if (!isUriAction(annotationAction)) continue; replaceUriWithLocalDestination(annotationAction); } } } } }
new ReplacePdfLinks { InputPdf = @"test.pdf", OutputPdf = "mod.pdf", UriToNamedDestination = uri => { if (uri.Host.ToLowerInvariant().Contains("google.com")) { return "entry1"; } return string.Empty; } }.Start();
annotationAction.Put(PdfName.URI, new PdfString("http://www.bing.com/"));
newLocalDestination.Add((PdfObject)_reader.GetPageOrigRef(pageNum: 2));RemovePdfLinks.7z
C:\Users\Vahid>dotnet --version 1.0.0-preview2-003121
.NET Core -> ASP.NET Core Web Application (.NET Core) -> Select `Empty` Template
{ "projects": [ "src", "test" ], "sdk": { "version": "1.0.0-preview2-003121" } }
MailMessage message = new MailMessage("from@site.com", strTo, subject, body)
{
IsBodyHtml = true,
BodyEncoding = Encoding.UTF8
};
SmtpClient client = new SmtpClient("127.0.0.1",portNumber);//portNumber is new
client.UseDefaultCredentials = false; //new
client.DeliveryMethod = SmtpDeliveryMethod.Network; //new
client.Credentials = new NetworkCredential("mail_user", "pass"); //new
client.Send(message);
فرض کنید یک صفحهی Blazor SSR، از سه کامپوننت منوی سمت راست، محتوای اصلی صفحه و فوتر سایت که به همراه متنی است، تشکیل شدهاست. منوی سمت راست، به همراه لینکهاییاست که آمار آنها را نیز نمایش میدهد و این اطلاعات را از بانک اطلاعاتی، به کمک EF-Core دریافت میکند. فوتر صفحه، سال شروع به کار و نام برنامه را از بانک اطلاعاتی دریافت میکند و محتوای اصلی صفحه نیز از بانک اطلاعاتی دریافت میشود. پس از تکمیل این سه کامپوننت مجزا، اگر برنامه را اجرا کنید، بلافاصله با خطای زیر مواجه میشوید:
A second operation started on this context before a previous operation completed
مشکل کجاست؟! مشکل اینجاست که تنها یک نمونه از DbContext، در طول درخواست جاری رسیده، بین سه کامپوننت جاری برنامه به اشتراک گذاشته میشود (به سازندهی سرویسهای مرتبط تزریق میشود) و ... در Blazor SSR، پردازش کامپوننتهای یک صفحه، به صورت موازی و همزمان انجام میشوند؛ یعنی ترتیبی نیست. اگر ابتدا کامپوننت منو، بعد محتوای صفحه و در آخر فوتر، رندر میشدند، هیچگاه پیام فوق را مشاهده نمیکردیم؛ اما ... هر سه کامپوننت، با هم و همزمان رندر میشوند و سپس نتیجهی نهایی در Response درج خواهد شد. یعنی یک DbContext بین چندین ترد به اشتراک گذاشته میشود که چنین حالتی توسط EF-Core پشتیبانی نمیشود و مجاز نیست.
روش مواجه شدن با یک چنین حالتهایی، نمونه سازی مجزای DbContext به ازای هر کامپوننت است که نمونهای از آنرا پیشتر در مطلب «نکات ویژهی کار با EF-Core در برنامههای Blazor Server» مشاهده کردهاید. در این مطلب، راهحل دیگری برای اینکار ارائه میشود که سادهتر است و نیازی به تغییرات آنچنانی در کدهای کامپوننتها و کل برنامه ندارد.
استفاده از کلاس پایهی OwningComponentBase برای نمونه سازی مجدد DbContext بهازای هر کامپوننت
زمانیکه در برنامههای Blazor SSR از روش استاندارد زیر برای دسترسی به سرویسهای مختلف برنامه استفاده میکنیم:
@inject IHotelRoomService HotelRoomService
طول عمر دریافتی سرویس، دقیقا بر اساس طول عمر اصلی تعریف شدهی آن عمل میکند (شبیه به برنامههای ASP.NET Core). یعنی برای مثال اگر Scoped باشد، DbContext تزریق شدهی در آن هم Scoped است و این DbContext، بین تمام کامپوننتهای در حال پردازش موازی در طول یک درخواست، بهاشتراک گذاشته میشود که مطلوب ما نیست. ما میخواهیم بتوانیم به ازای هر کامپوننت مجزای صفحه، یک DbContext جدید داشته باشیم. یعنی باید بتوانیم خودمان این سرویس Scoped را نمونه سازی کنیم و نه اینکه آنرا مستقیما از سیستم تزریق وابستگیها دریافت کنیم.
بنابراین اگر بخواهیم قسمتهای مختلف برنامه را تغییر ندهیم و همان تعاریف ابتدایی services.AddDbContext و Scoped تعریف کردن سرویسهای برنامه بدون تغییر باقی بمانند (و از IDbContextFactory و موارد مشابه دیگر مطلب «نکات ویژهی کار با EF-Core در برنامههای Blazor Server» هم استفاده نکنیم)، باید جایگزینی را برای نمونه سازی سرویسها ارائه دهیم. به همین جهت در ابتدا، یک ویژگی جدیدی را به صورت زیر تعریف میکنیم:
[AttributeUsage(AttributeTargets.Property)] public sealed class InjectComponentScopedAttribute : Attribute { }
تا بتوانیم بجای:
@inject IHotelRoomService HotelRoomService
بنویسیم:
[InjectComponentScoped] internal IHotelRoomService HotelRoomService { set; get; } = null!;
مرحلهی بعد، نوبت به نمونه سازی خودکار این سرویسهای درخواستی علامتگذاری شدهی با InjectComponentScoped است. برای این منظور، تمام کامپوننتهای برنامه را از کلاس پایه و استاندارد OwningComponentBase ارثبری میکنیم. مزیت اینکار، امکان دسترسی به خاصیتی به نام ScopedServices در تمام کامپوننتهای برنامه است که توسط آن میتوان به متد ScopedServices.GetRequiredService آن دسترسی یافت. یعنی با ارثبری از کلاس پایهی OwningComponentBase به ازای هر کامپوننت، به صورت خودکار Scope جدیدی شروع میشود که توسط آن میتوان به نمونهی جدیدی از سرویس مدنظر دسترسی یافت و نه به نمونهی اشتراکی در طی درخواست جاری.
اکنون اگر از این مزیت به صورت زیر استفاده کنیم، میتوان تمام سرویسهای درخواستی مزین به InjectComponentScopedAttribute یک کامپوننت را به صورت خودکار یافته و با استفاده از ScopedServices.GetRequiredService، مقدار دهی کرد:
public class BlazorScopedComponentBase : OwningComponentBase { private static readonly ConcurrentDictionary<Type, Lazy<List<PropertyInfo>>> CachedProperties = new(); private List<PropertyInfo> InjectComponentScopedPropertiesList => CachedProperties.GetOrAdd(GetType(), type => new Lazy<List<PropertyInfo>>( () => type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) .Where(p => p.GetCustomAttribute<InjectComponentScopedAttribute>() is not null) .ToList(), LazyThreadSafetyMode.ExecutionAndPublication)).Value; protected override void OnInitialized() { foreach (var propertyInfo in InjectComponentScopedPropertiesList) { propertyInfo.SetValue(this, ScopedServices.GetRequiredService(propertyInfo.PropertyType)); } } }
این سرویس، اینبار طول عمری، محدود به کامپوننت جاری را خواهد داشت و بین سایر کامپوننتهای درحال پردازش درخواست جاری، به اشتراک گذاشته نمیشود و همچنین به صورت خودکار هم در پایان درخواست، Dispose میشود.
فعالسازی ارثبری خودکار در تمام کامپوننتهای برنامه
مرحلهی بعد، ارثبری خودکار تمام کامپوننتهای برنامه از OwningComponentBase سفارشی فوق است و در اینجا قصد نداریم تمام کامپوننتها را جهت معرفی آن، به صورت دستی تغییر دهیم. برای اینکار فقط کافی است به فایل Imports.razor_ مراجعه و یک سطر زیر را در آن درج کنیم:
@inherits BlazorScopedComponentBase
با اینکار یک ارثبری سراسری در کل برنامه رخ میدهد و تمام کامپوننتها، از BlazorScopedComponentBase مشتق خواهند شد. یعنی پس از این تغییر، اگر سرویسی را به صورت زیر معرفی و با ویژگی InjectComponentScoped علامتگذاری کردیم:
[InjectComponentScoped] internal IHotelRoomService HotelRoomService { set; get; } = null!;
به صورت خودکار یافت شده و نمونه سازی Scoped محدود به طول عمر همان کامپوننت میشود که بین سایر کامپوننتها، به اشتراک گذاشته نخواهد شد.
یک نکته: اگر کامپوننت شما متد OnInitialized را بازنویسی میکند، فراموش نکنید که در ابتدای آن باید ()base.OnInitialized را هم فراخوانی کنید تا متد OnInitialized کامپوننت پایهی BlazorScopedComponentBase نیز فراخوانی شود. البته این مورد در حین بازنویسی نمونهی async آن مهم نیست؛ چون همیشه OnInitialized غیر async در ابتدا فراخوانی میشود و سپس نمونهی async آن اجرا خواهد شد.
const electron = require('electron'); const {app,dialog,BrowserWindow,Menu,shell} = electron; let win; app.on('ready', function () { win = new BrowserWindow({width: 800, height: 600}); win.loadURL(`file://${__dirname}/index.html`); var app_menu=[ { label:'پرونده', submenu:[ { label:'باز کردن', accelerator:'CmdOrCtrl+O', click:()=>{ } }, { label:'ذخیره', accelerator:'CmdOrCtrl+S', click:()=>{ } } ] }, { label:'سیستم', submenu:[ { label:'درباره ما', click:()=> { shell.openExternal('https://www.dntips.ir'); } }, { label:'خروج', accelerator:'CmdOrCtrl+X', click:()=> { win=null; app.quit(); } } ] } ];
if(process.platform=="darwin") { const app_name=app.getName(); app_menu.unshift({ label:app_name }) }
ویندوز | win32 حتی اگر 64 بیتی باشد. |
لینوکس | linux |
مک | darwin |
فری بی اس دی | freebsd |
سولاریس | sunos |
دستو shell در بالا به شما اجازه میدهد با محیط دسکتاپ، یکپارچگی خود
را حفظ کنید و دستوراتی از قبیل باز کردن url، باز کردن یک مسیر دایرکتوری،
باز کردن یک فایل، انتقال فایل به سطل آشغال یا بازیافت و صدای بوق سیستم
(بیپ) را به شما میدهد. مستندات این شیء را در اینجا مطالعه فرمایید.
{ label:'خروج', accelerator:'CmdOrCtrl+X', role:'close' }
var menu=Menu.buildFromTemplate(app_menu); Menu.setApplicationMenu(menu);
dialog.showOpenDialog({ title:'باز کردن فایل متنی', properties: [ 'openFile']//[ 'openFile', 'openDirectory', 'multiSelections' ] ,filters:[ {name:'فایلهای نوشتاری' , extensions:['txt','text']}, {name:'جهت تست' , extensions:['doc','docx']} ] }, (filename)=>{ if(filename===undefined) return; dialog.showMessageBox({title:'پیام اطلاعاتی',type:"info",buttons:['تایید'],message:`the name of file is [${filename}]`}); });
dialog.showSaveDialog({ title:'باز کردن فایل متنی', properties: [ 'openFile']//[ 'openFile', 'openDirectory', 'multiSelections' ] ,filters:[ {name:'فایلهای نوشتاری' , extensions:['txt','text']} ] }, (filename)=>{ if(filename===undefined) return; });
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> Fie Content:<br/> <textarea id="TextFile" cols="100" rows="50"></textarea> </body> </html>
dialog.showOpenDialog({ title:'باز کردن فایل متنی', properties: [ 'openFile']//[ 'openFile', 'openDirectory', 'multiSelections' ] ,filters:[ {name:'فایلهای نوشتاری' , extensions:['txt','text']}, {name:'جهت تست' , extensions:['doc','docx']} ] }, (filename)=>{ if(filename===undefined) return; win.webContents.send('openFile',filename); // dialog.showMessageBox({title:'پیام اطلاعاتی',type:"info",buttons:['تایید'],message:`the name of file is [${filename}]`}); });
dialog.showSaveDialog({ title:'باز کردن فایل متنی', properties: [ 'openFile']//[ 'openFile', 'openDirectory', 'multiSelections' ] ,filters:[ {name:'فایلهای نوشتاری' , extensions:['txt','text']} ] }, (filename)=>{ if(filename===undefined) return; win.webContents.send('saveFile',filename); });
<script> const {ipcRenderer} = require('electron'); var fs=require('fs'); ipcRenderer.on('openFile', (event, arg) => { var content= fs.readFileSync(String(arg),'utf8'); document.getElementById("TextFile").value=content; }); ipcRenderer.on('saveFile', (event, arg) =>{ var content=document.getElementById("TextFile").value; fs.writeFileSync(String(arg),content,'utf8'); alert('ذخیره شد'); }); </script>
using System;
using System.IO;
using System.IO.Compression;
using System.Globalization;
using System.Web;
public class JsonCompressionModule : IHttpModule
{
public JsonCompressionModule()
{
}
public void Dispose()
{
}
public void Init(HttpApplication app)
{
app.PreRequestHandlerExecute += new EventHandler(Compress);
}
private void Compress(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
HttpRequest request = app.Request;
HttpResponse response = app.Response;
if (request.ContentType.ToLower(CultureInfo.InvariantCulture).StartsWith("application/json"))
{
if (!((request.Browser.IsBrowser("IE")) && (request.Browser.MajorVersion <= 6)))
{
string acceptEncoding = request.Headers["Accept-Encoding"];
if (!string.IsNullOrEmpty(acceptEncoding))
{
acceptEncoding = acceptEncoding.ToLower(CultureInfo.InvariantCulture);
if (acceptEncoding.Contains("gzip"))
{
response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
response.AddHeader("Content-encoding", "gzip");
}
else if (acceptEncoding.Contains("deflate"))
{
response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
response.AddHeader("Content-encoding", "deflate");
}
}
}
}
}
}
if ( !request.Url.PathAndQuery.ToLower().Contains( ".asmx" ) )
return;
<httpModules>
<add name="JsonCompressionModule" type="JsonCompressionModule"/>
</httpModules>