If you maintain an open-source project in the range of 10k-200k lines of code, I strongly encourage you to add an ARCHITECTURE document next to README and CONTRIBUTING. Before going into the details of why and how, I want to emphasize that this is not another “docs are good, write more docs” advice. I am pretty sloppy about documentation, and, eg, I often use just “simplify” as a commit message. Nonetheless, I feel strongly about the issue, even to the point of pestering you:-)
This post shows how to import and export .xls or .xlsx (Excel files) in ASP.NET Core. And when thinking about dealing with excel with .NET, we always look for third-party libraries or component. And one of the most popular .net library that reads and writes Excel 2007/2010 files using the Open Office Xml format (xlsx) is EPPlus. However, at the time of writing this post, this library is not updated to support .NET Core. But there exists an unofficial version of this library EPPlus.Core which can do the job of import and export xlsx in ASP.NET Core. This works on Windows, Linux and Mac.
دریافت پارامترهای مسیریابی
گاهی از اوقات نیاز به ارسال پارامترهایی به مسیریابیهای تعریف شدهاست. برای مثال در لیست محصولات تعریف شده، بسته به انتخاب هر کدام، باید id متناظری نیز در URL نهایی ظاهر شود. به این Id، یک Route parameter گفته میشود. برای پیاده سازی این نیازمندی به صورت زیر عمل میکنیم:
در فایل app.js، یک مسیریابی جدید را برای نمایش جزئیات یک محصول اضافه میکنیم:
import ProductDetails from "./components/productDetails"; // ... class App extends Component { render() { return ( <div> <NavBar /> <div className="container"> <Switch> <Route path="/products/:id" component={ProductDetails} /> <Route path="/products" render={props => ( <Products param1="123" param2="456" {...props} /> )} /> <Route path="/posts/:year/:month" component={Posts} /> <Route path="/admin" component={Dashboard} /> <Route path="/" component={Home} /> </Switch> </div> </div> ); } }
کامپوننت جدید src\components\productDetails.jsx نیز به صورت زیر تعریف شدهاست:
import React, { Component } from "react"; class ProductDetails extends Component { handleSave = () => { // Navigate to /products }; render() { return ( <div> <h1>Product Details - </h1> <button className="btn btn-primary" onClick={this.handleSave}> </div> ); } } export default ProductDetails;
در اینجا با گشودن اطلاعات خاصیت match تزریق شدهی به کامپوننت ProductDetails، میتوان اطلاعاتی مانند پارامترهای دریافتی مسیریابی را دقیقا مشاهده کرد. برای مثال در این تصویر id=1 از URL بالای صفحه که به http://localhost:3000/products/1 تنظیم شدهاست، دریافت میشود.
بنابراین امکان خواندن اطلاعات پارامترهای مسیریابی، توسط شیء match تزریق شدهی به یک کامپوننت وجود دارد. به همین جهت کامپوننت ProductDetails را ویرایش کرده و المان h1 آنرا جهت نمایش id محصول به صورت زیر تغییر میدهیم که در آن شیء match.params، از props کامپوننت تامین میشود:
<h1>Product Details - {this.props.match.params.id} </h1>
برای آزمایش آن مجددا از صفحهی products شروع کرده و بر روی لینک یکی از محصولات، کلیک کنید. در اینجا هرچند id محصول به درستی نمایش داده میشود، اما ... نمایش جزئیات آن به همراه بارگذاری کامل و مجدد صفحهی آن است که از حالت SPA خارج شدهاست. برای رفع این مشکل به کامپوننت products مراجعه کرده و anchorهای تعریف شده را همانطور که در قسمت قبل نیز بررسی کردیم، تبدیل به کامپوننت Link میکنیم.
از حالت قبلی:
{this.state.products.map(product => ( <li key={product.id}> <a href={`/products/${product.id}`}>{product.name}</a> </li> ))}
import { Link } from "react-router-dom"; // ... <Link to={`/products/${product.id}`}>{product.name}</Link>
پارامترهای اختیاری مسیریابی
به تعریف مسیریابی زیر، دو پارامتر سال و ماه، اضافه شدهاند:
<Route path="/posts/:year/:month" component={Posts} />
برای رفع این مشکل میتوان با اضافه کردن یک ? به هر پارامتر، پارامترهای تعریف شده را اختیاری کرد:
<Route path="/posts/:year?/:month?" component={Posts} />
با این تغییرات اگر مجددا آدرس http://localhost:3000/posts/2018 را درخواست کنیم، کامپوننت Posts بجای کامپوننت Home نمایش داده میشود.
اکنون کامپوننت Posts را به صورت زیر تغییر میدهیم تا پارامترهای مسیریابی را نیز درج کند:
import React from "react"; const Posts = ({ match }) => { return ( <div> <h1>Posts</h1> Year: {match.params.year} , Month: {match.params.month} </div> ); }; export default Posts;
پس از ذخیره سازی این تغییرات و بارگذاری مجدد برنامه در مرورگر، اگر آدرس http://localhost:3000/posts/2018/1 را وارد کنیم، خروجی زیر حاصل میشود:
کار با پارامترهای کوئری استرینگهای مسیریابی
پارامترهای اختیاری، جزو قابلیتهایی هستند که باید تا حد ممکن از بکارگیری آنها اجتناب و آنها را با کوئری استرینگها تعریف کرد. کوئری استرینگها با یک ? در انتهای URL شروع میشوند و میتوانند چندین پارامتر را داشته باشند؛ مانند: http://localhost:3000/posts?sortBy=newest&approved=true و یا حتی میتوان آنها را با پارامترهای اختیاری نیز ترکیب کرد مانند: http://localhost:3000/posts/2018/05?sortBy=newest&approved=true
برای استخراج کوئری استرینگها در برنامههای React باید از شیء location استفاده کرد:
در اینجا مقدار خاصیت search، کل قسمت کوئری استرینگها را به همراه دارد. البته ما قصد پردازش آنرا به صورت دستی نداریم. به همین جهت از کتابخانهی زیر برای انجام اینکار استفاده خواهیم کرد:
> npm i query-string --save
import queryString from "query-string"; const Posts = ({ match, location }) => { const result = queryString.parse(location.search); console.log(result); // ...
- سپس شیء queryString را از ماژول مرتبط، import میکنیم. در ادامه به کمک متد parse آن، میتوان location.search را آنالیز کرد که خروجی آن، یک شیء جاوا اسکریپتی به صورت زیر است:
{approved: "true", sortBy: "newest"}
const { approved, sortBy } = queryString.parse(location.search);
یک نکته: باید دقت داشت که کتابخانهی query-string، همیشه مقادیر خواص را رشتهای بازگشت میدهد؛ حتی اگر عدد باشند.
مدیریت مسیرهای نامعتبر درخواستی
فرض کنید کاربری آدرس غیرمعتبر http://localhost:3000/xyz را که هیچ نوع مسیریابی را برای آن تعریف نکردهایم، درخواست میکند. در این حالت برنامه کامپوننت home را رندر میکند که مرتبط است با تعاریف مسیریابی برنامه در فایل app.js. تنها path تعریف شدهای که با این آدرس تطابق پیدا میکند، / متناظر با کامپوننت home است.
بجای این رفتار پیشفرض، مایل هستیم که کاربر به یک صفحهی سفارشی «پیدا نشد» هدایت شود. به همین جهت ابتدا کامپوننت جدید تابعی بدون حالت src\components\notFound.jsx را با محتوای زیر ایجاد میکنیم:
import React from "react"; const NotFound = () => { return <h1>Not Found</h1>; }; export default NotFound;
<Route path="/" exact component={Home} />
- ابتدا شیء Redirect را از react-router-dom باید import کنیم.
- همچنین import کامپوننت NotFound نیز باید ذکر شود.
- سپس پیش از مسیریابی کلی /، مسیریابی جدید not-found را که به کامپوننت NotFound اشاره میکند، اضافه میکنیم.
- اکنون در انتهای Switch تعریف شده (جائی که دیگر هیچ مسیریابی تعریف شدهای، با مسیر درخواستی کاربر، تطابق نداشته)، باید کامپوننت Redirect را جهت هدایت به این مسیریابی جدید، تعریف کرد:
import { Redirect, Route, Switch } from "react-router-dom"; //... import NotFound from "./components/notFound"; //... class App extends Component { render() { return ( <div> <NavBar /> <div className="container"> <Switch> //... <Route path="/not-found" component={NotFound} /> <Route path="/" exact component={Home} /> <Redirect to="/not-found" /> </Switch> </div> </div> ); } }
کاربرد دیگر کامپوننت Redirect، هدایت کاربران از یک آدرس قدیمی، به یک آدرس جدید است که نحوهی تعریف آن به صورت زیر میباشد:
<Redirect from="/messages" to="/posts" />
هدایت کاربران به آدرسهای مختلف با برنامه نویسی
گاهی از اوقات پس از تکمیل فرمی و یا کلیک بر روی دکمهای، میخواهیم کاربر را به آدرس خاصی هدایت کنیم. برای مثال در برنامهی جاری میخواهیم زمانیکه کاربری صفحهی جزئیات یک محصول را مشاهده و بر روی دکمهی فرضی Save کلیک کرد، دوباره به همان صفحهی لیست محصولات هدایت شود؛ برای این منظور از شیء history استفاده خواهیم کرد:
در اینجا متدها و خواص شیء history را مشاهده میکنید. برای نمونه توسط متد push آن میتوان آدرس جدیدی را به تاریخچهی آدرسهای مرور شدهی توسط کاربر، اضافه کرد و کاربر را با برنامه نویسی، به صفحهی جدیدی هدایت نمود:
class ProductDetails extends Component { handleSave = () => { // Navigate to /products this.props.history.push("/products"); };
یک نکته: اگر به تصویر دقت کنید، متد replace هم در اینجا قابل استفاده است. متد push با افزودن رکوردی به تاریخچهی آدرسهای مرور شدهی در مرورگر، امکان بازگشت به محل قبلی را با کلیک بر روی دکمهی back مرورگر، فراهم میکند؛ اما replace تنها رکورد آدرس جاری را در تاریخچهی مرورگر به روز رسانی میکند. یعنی از داشتن تاریخچه محروم خواهیم شد. عمدهی کاربرد این متد، در صفحات لاگین است؛ زمانیکه کاربر به سیستم وارد میشود و به صفحهی جدیدی مراجعه میکند، با کلیک بر روی دکمهی back، دوباره نمیخواهیم او را به صفحهی لاگین هدایت کنیم.
تعریف مسیریابیهای تو در تو
قصد داریم به صفحهی admin، دو لینک جدید به مطالب و کاربران ادمین را اضافه کنیم، به نحوی که با کلیک بر روی هر کدام، محتوای هر صفحهی متناظر، در همینجا نمایش داده شود (تصویر فوق). به عبارتی در حال حاضر، مسیریابی تعریف شده، جهت مدیریت لینکهای NavBar بالای صفحه کار میکند. اکنون میخواهیم مسیریابی دیگری را داخل آن برای پوشش منوی کنار صفحهی ادمین اضافه کنیم. به اینکار، تعریف مسیریابیهای تو در تو گفته میشود و پیاده سازی آن توسط کامپوننت react-router-dom بسیار سادهاست و شامل این موارد میشود:
- ابتدا مسیریابیهای جدید را همینجا داخل کامپوننت src\components\admin\dashboard.jsx تعریف میکنیم:
const Dashboard = ({ match }) => { return ( <div> <h1>Admin Dashboard</h1> <div className="row"> <div className="col-3"> <SideBar /> </div> <div className="col"> <Route path="/admin/users" component={Users} /> <Route path="/admin/posts" component={Posts} /> </div> </div> </div> ); };
تنها نکتهی جدید آن، امکان درج کامپوننت Route در قسمتهای مختلف برنامه، خارج از app.js میباشد و این امکان محدود به app.js نیست. در این حالت اگر مسیر /admin/posts توسط کاربر وارد شد، دقیقا در همان محلی که کامپوننت Route درج شدهاست، کامپوننت متناظر با این مسیر یعنی کامپوننت Posts، رندر میشود.
در ادامه محتوای این کامپوننتهای جدید را نیز ملاحظه میکنید:
محتوای کامپوننت src\components\admin\sidebar.jsx
import React from "react"; import { Link } from "react-router-dom"; const SideBar = () => { return ( <ul className="list-group"> <li className="list-group-item"> <Link to="/admin/posts">Posts</Link> </li> <li className="list-group-item"> <Link to="/admin/users">Users</Link> </li> </ul> ); }; export default SideBar;
محتوای کامپوننت src\components\admin\users.jsx
import React from "react"; const Users = () => { return <h1>Admin Users</h1>; }; export default Users;
محتوای کامپوننت src\components\admin\posts.jsx
import React from "react"; const Posts = () => { return ( <div> <h1>Admin Posts</h1> </div> ); }; export default Posts;
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-15-part-02.zip
Fixed In This Release of Visual Studio 2019 version 16.5
- Team Explorer not loading after update to mandatory latest visual studio version for Visual studio 2019
- Find Highlighting Fails when Matching with Match Case Disabled and Regex Option Enabled
Security Advisory Notice
CVE-2020-1108 .NET Core Denial of Service Vulnerability
A remote unauthenticated attacker could exploit this vulnerability by issuing specially crafted requests to the .NET Core application. The security update addresses the vulnerability by correcting how the .NET Core web application handles web requests.
CVE-2020-1161 .NET Core Denial of Service Vulnerability
A remote unauthenticated attacker could exploit this vulnerability by issuing specially crafted requests to the ASP.NET Core application. The security update addresses the vulnerability by correcting how the ASP.NET Core web application handles web requests.
WPF allows you to build modern desktop applications for Windows, and part of building an application is debugging code and optimizing performance. In Alessandro Del Sole’s WPF Debugging and Performance Succinctly, you will learn how to debug a WPF application by leveraging all the powerful tools in Visual Studio, including the most recent additions that allow you to investigate the behavior of the UI at runtime. Also, you will learn how to analyze and improve an application’s performance in order to provide your customers with the best possible experience and thereby make them happy.
- Debugging WPF Applications
- Stepping Through Code
- Working with Debug Windows
- Debugger Visualizers and Trace Listeners
- XAML Debugging
- Analyzing the UI Performances
- Analyzing the Application Performances
public class TestController { private readonly ILogger<TestController> _logger; public TestController(ILogger<TestController> logger) { _logger = logger; } [HttpGet("/")] public string Get() { _logger.LogInformation("hello world"); return "Hello world!"; } }
اگر یک چنین برنامهای را به دات نت 6 ارتقاء دهید، با پیام اخطار زیر مواجه خواهید شد:
CA1848: For improved performance, use the LoggerMessage delegates instead of calling LogInformation
استفادهی گسترده از source generators در دات نت 6
source generators، امکان مداخله در عملیات کامپایل برنامه را میسر کرده و امکان تولید کدهای پویایی را در زمان کامپایل، فراهم میکنند. هرچند این قابلیت به همراه دات نت 5 ارائه شدند، اما تا زمان دات نت 6 استفادهی گستردهای از آن در خود دات نت صورت نگرفت. موارد زیر، تغییراتی است که بر اساس source generators در دات نت 6 رخ دادهاند:
- source generators مخصوص ILogger (موضوع این بحث؛ یعنی LoggerMessage source generator)
- source generators مخصوص System.Text.Json تا سربار تبدیل به JSON و یا برعکس کمتر شود.
- بازنویسی مجدد پروسهی کامپایل Blazor/Razor بر اساس source generators، بجای روش دو مرحلهای قبلی که امکان Hot Reload را فراهم کردهاست.
نوشتن یک source generator هرچند ساده نیست، اما چون نیاز به reflection را به حداقل میرساند، میتواند تغییرات کارآیی بسیار مثبتی را به همراه داشته باشد.
توصیه به استفاده از LoggerMessage.Define در دات نت 6
ILogger به همراه قابلیتهایی مانند structural logging نیز هست که امکان فرمت بهتر پیامهای ثبت شده را میسر میکند تا توسط برنامههای جانبی که قرار است این لاگها را پردازش کنند، به سادگی قابل خواندن باشند. برای مثال رکورد زیر را در نظر بگیرید:
public record Person (int Id, string Name);
var person = new Person(123, "Test");
_logger.LogInformation("hello to {Person}", person);
info: TestController[0] hello world to Person { Id = 123, Name = Test }
اگر در اینجا مانند مثال زیر از string interpolation استفاده شود:
_logger.LogInformation($"hello world to {person}"); // Using interpolation instead of structured logging
- ویژگی «لاگهای ساختار یافته» را از دست میدهیم و دیگر توسط نرم افزارهای ثالث لاگ خوان، به سادگی پردازش نخواهند شد.
- ویژگی «قالب ثابت» پیام را نیز از دست خواهیم داد که باز هم یافتن پیامهای مشابه را در بین انبوهی از لاگهای رسیده مشکل میکند.
- کار serialization شیء ارسالی به آن، پیش از عملیات ثبت وقایع رخ میدهد. اما ممکن است سطح لاگ سیستم در این حد نباشد و اصلا این پیام لاگ نشود. در این حالت یک کار اضافی صورت گرفته و بر روی کارآیی برنامه تاثیر منفی خواهد گذاشت.
برای جلوگیری از serialization و همچنین تخصیص حافظهی اضافی و مشکلات عدم ساختار یافته بودن لاگها، توصیه شدهاست که ابتدا سطح لاگ مدنظر بررسی شود و همچنین از string interpolation استفاده نشود:
if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("hello world to {Person}", person); }
مشکل دیگر لاگهای ساختار یافته، امکان فراموش کردن یکی از پارامترها است که با یک خطای زمان اجرا گوشزد خواهد شد؛ مانند مثال زیر:
_logger.LogInformation("hello world to {Person} because {Reason}", person);
private static readonly Action<ILogger, Person, Exception?> _logHelloWorld = LoggerMessage.Define<Person>( logLevel: LogLevel.Information, eventId: 0, formatString: "hello world to {Person}");
اکنون روش فراخوانی این Action با کارآیی بالا به صورت زیر است:
[HttpGet("/")] public string Get() { var person = new Person(123, "Test"); _logHelloWorld(_logger, person, null); return "Hello world!"; }
معرفی [LoggerMessage] source generator در دات نت 6
هرچند LoggerMessage.Define، مزایای قابل توجهی مانند کش شدن قالب لاگ، عدم نیاز به بررسی ضرورت لاگ شدن پیام و ارسال تعداد پارامترهای صحیح را به همراه دارد، اما ... کار کردن با آن مشکل است و برای کار با آن باید کدهای زیادی را نوشت. به همین جهت با استفاده از قابلیت source generators، امکان تولید خودکار این نوع کدها در زمان کامپایل برنامه پیشبینی شدهاست:
public partial class TestController { [LoggerMessage(0, LogLevel.Information, "hello world to {Person}")] partial void LogHelloWorld(Person person); }
ویژگی partial method، امکان تعریف یک متد را در یک فایل و سپس ارائهی پیاده سازی آنرا در فایلی دیگر میسر میکند که البته در اینجا آن فایل دیگر، توسط source generator تولید میشود.
باید دقت داشت که در اینجا TestController را نیز باید به صورت partial تعریف کرد تا آن نیز قابلیت بسط در چند فایل را پیدا کند. همچنین متد فوق را به صورت static partial void نیز میتوان نوشت.
یکی از مزایای کار با source generator که خودش در اصل یک آنالایزر هم هست، بررسی تعداد پارامترهای ارسالی و تعریف شدهاست:
[LoggerMessage(0, LogLevel.Information, "hello world to {Person} with a {Reason}")] partial void LogHelloWorld(Person person);
در این روش، امکان ذکر پارامتر اختیاری LogLevel هم وجود دارد؛ اگر نیاز است مقدار آن به صورت پویا تغییر کند:
[LoggerMessage(Message = "hello world to {Person}")] partial void LogHelloWorld(LogLevel logLevel, Person person);
int intValue = 182; string hexValue = intValue.ToString("X"); int intAgain = int.Parse(hexValue, System.Globalization.NumberStyles.HexNumber);
var hexValue = "0x00010471000001BF001F";
روش اول:
byte[] bigNumber = new byte[] { 0, 0, 0, 1, 0, 4, 7, 1, 0, 0, 0, 0, 0, 1, byte.Parse("B", System.Globalization.NumberStyles.HexNumber), byte.Parse("F", System.Globalization.NumberStyles.HexNumber), 0, 0, 1, byte.Parse("F", System.Globalization.NumberStyles.HexNumber) }; Array.Reverse(bigNumber); var bigInt = new System.Numerics.BigInteger(bigNumber);
روش دوم:
var bigint = System.Numerics.BigInteger.Parse("00010471000001BF001F", System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture);
منابع: