//new group $("#NewGroup").on('click', function () { var catName = 'new';// $("#appendNodeText").val(); $.ajax({ dataType: "json", type: "POST", url: "@Url.Action(MVC.Categories.NewCategory())", data: { name: catName } }).done(function (data) { alert(data); }); });
ASP.NET MVC #18
public static int ToInt(this string data) { if (string.IsNullOrWhiteSpace(data)) return 0; int result; return int.TryParse(data, out result) ? result : 0; }
FormsAuthentication.SetAuthCookie(user.Id.ToString(CultureInfo.InvariantCulture), ... // ... FormsAuthentication.RedirectFromLoginPage(user.Id.ToString(CultureInfo.InvariantCulture), ...
- نصب پایتون 2.5 یا 2.6 یا 2.7 که فعلا در سایت آن، نسخهی 2.7 در دسترس هست. توجه داشته باشید که هنوز برای نسخهی 3 پایتون پشتیبانی صورت نگرفته است.
- آخرین نسخهی sdk را هم میتوانید از این آدرس به صورت zip و یا از این آدرس به صورت tar دانلود کنید و در صورتیکه دوست دارید به سورس آن دسترسی داشته باشید یا اینکه از سورسهای مشارکت شده یا غیر رسمی استفاده کنید، از این صفحه آن را دریافت کنید.
(C:\Users\aym\Downloads\addon-sdk-1.17) C:\Users\aym\Downloads\addon-sdk-1.17\bin>
source bin/activate
bash bin/activate
(addon-sdk)~/mozilla/addon-sdk >
آغاز به کار
mkdir fxaddon cd fxaddon cfx init
* lib directory created * data directory created * test directory created * doc directory created * README.md written * package.json written * test/test-main.js written * lib/main.js written * doc/main.md written Your sample add-on is now ready for testing: try "cfx test" and then "cfx run". Have fun!"
{ "name": "fxaddon", "title": "fxaddon", "id": "jid1-QfyqpNby9lTlcQ", "description": "a basic add-on", "author": "", "license": "MPL 2.0", "version": "0.1" }
{ "name": "dotnettips", "title": ".net Tips Updater", "id": "jid1-QfyqpNby9lTlcQ", "description": "This extension keeps you updated on current activities on dotnettips.info", "author": "yeganehaym@gmail.com", "license": "MPL 2.0", "version": "0.1" }
var button= require('sdk/ui/button/action');
buttons.ActionButton({...});
var tgbutton = require('sdk/ui/button/toggle'); var panels = require("sdk/panel"); var self = require("sdk/self"); var button = tgbutton.ToggleButton({ id: "updaterui", label: ".Net Updater", icon: { "16": "./icon-16.png", "32": "./icon-32.png", "64": "./icon-64.png" }, onChange: handleChange }); var panel = panels.Panel({ contentURL: self.data.url("./popup.html"), onHide: handleHide }); function handleChange(state) { if (state.checked) { panel.show({ position: button }); } } function handleHide() { button.state('window', {checked: false}); }
tgbutton.ToggleButton
require('sdk/ui/button/toggle').ToggleButton
Context Menus
var contextMenu = require("sdk/context-menu"); var home = contextMenu.Item({ label: "صفحه اصلی", data: "https://www.dntips.ir/" }); var postsarchive = contextMenu.Item({ label: "مطالب سایت", data: "https://www.dntips.ir/postsarchive" }); var menuItem = contextMenu.Menu({ label: "Open .Net Tips", context: contextMenu.PageContext(), items: [home, postsarchive], image: self.data.url("icon-16.png"), contentScript: 'self.on("click", function (node, data) {' + ' window.location.href = data;' + '});' });
SelectorContext("img")
SelectorContext("img,a[href]")
var tabs = require("sdk/tabs"); var menuItem = contextMenu.Menu({ label: "Open .Net Tips", context: contextMenu.PageContext(), items: [home, postsarchive], image: self.data.url("icon-16.png"), contentScript: 'self.on("click", function (node, data) {' + ' self.postMessage(data);' + '});', onMessage: function (data) { tabs.open(data); } });
var Url="https://www.dntips.ir/search?term="; var searchMenu = contextMenu.Item({ label: "search for", context: [contextMenu.PredicateContext(checkText),contextMenu.SelectionContext()], image: self.data.url("icon-16.png"), contentScript: 'self.on("click", function () {' + ' var text = window.getSelection().toString();' + ' if (text.length > 20)' + ' text = text.substr(0, 20);' + ' self.postMessage(text);'+ '})', onMessage: function (data) { tabs.open(Url+data); } }); function checkText(data) { if(data.selectionText === null) return false; console.log('selectionText: ' + data.selectionText); //handle showing or hiding of menu items based on the text content. menuItemToggle(data.selectionText); return true; }; function menuItemToggle(text){ var searchText="جست و جو برای "; searchMenu.label=searchText+text; };
در قسمت آینده موارد بیشتری را در مورد افزونه نویسی در فایرفاکس بررسی خواهیم کرد و افزونه را تکمیل خواهیم کرد
ابتدا شیء user، در بالاترین سطح، دریافت شده و به صفحهای خاص از طریق ویژگیهای props ارسال میشود:
<Page user={user} />
<PageLayout user={user} />
<NavigationBar user={user} />
ایجاد شیء Context در برنامههای React
React Context، راه حلی است جهت به اشتراک گذاری دادهها، در بین انواع و اقسام کامپوننتهای یک برنامه، بدون اینکه نیازی باشد این اطلاعات را توسط props، از یک سطح، به سطحی دیگر، به صورت دستی انتقال داد. برای ایجاد یک نمونهی از آن، ابتدا پوشهی جدید src\contexts را افزوده و سپس فایل src\contexts\userContext.js را درون آن، با محتوای زیر ایجاد میکنیم:
import React from "react"; export const UserContext = React.createContext({ user: {} }); export const UserProvider = UserContext.Provider; export const UserConsumer = UserContext.Consumer;
تامین یک شیء Context در برنامه، در یک کامپوننت کلاسی و یا تابعی
تا اینجا یک شیء Context را به همراه اجزای export شدهی Provider و Consumer آن ایجاد کردیم. اکنون نوبت به پیاده سازی قسمت Provider آن است:
import "../../App.css"; import React, { Component } from "react"; import { UserProvider } from "../../contexts/userContext"; import Main from "./Main"; class App extends Component { state = { user: { name: "User 1" } }; componentDidMount() { // get user from the server or local storage and then set the currently logged in user to the this.state } render() { return ( <> <h1>App Class</h1> <UserProvider value={this.state.user}> <Main /> </UserProvider> </> ); } } export default App;
در ادامه قصد داریم اطلاعات این شیء user موجود در state را با تمام کامپوننتهایی که در درخت رندر کامپوننت جاری قرار میگیرند و با کامپوننت Main شروع میشوند، به اشتراک بگذاریم. این به اشتراک گذاری با import شیء UserProvider از ماژول contexts/userContext به نحوی که مشاهده میکنید، انجام میشود. شیء UserProvider، کار محصور سازی کامپوننت Main را انجام میدهد. سپس این Provider میتواند مقداری را توسط ویژگی value خود دریافت کند که برای مثال در اینجا شیء user است. اکنون این value تا n سطح بعدی که از کامپوننت Main مشتق میشوند نیز در دسترس خواهد بود.
یک نکته: متد React.createContext به همراه یک آرگومان defaultValue اختیاری است که در اختیار Consumerهای آن قرار داده میشود؛ اگر Provider متناظر با آن، در درخت کامپوننتهای برنامه، یافت نشود. یعنی تعریف Provider الزامی نیست. اگر نیاز است مقدار ثابتی را بین چندین کامپوننت به اشتراک بگذارید، فقط کافی است آنها را توسط React.createContext مقدار دهی اولیه کرده و ... استفاده کنید:
export const DefaultRouteContext = React.createContext({ path: '/welcome' });
خواندن شیء Context در کامپوننتی دیگر
اکنون که یک تامین کنندهی Context را ایجاد کردیم، برای خواندن اطلاعات آن در درخت کامپوننتهای محصور شدهی توسط UserProvider، میتوان به صورت زیر عمل کرد:
import React from "react"; import { UserConsumer } from "../../contexts/userContext"; export default function Main(props) { return ( <> <UserConsumer> {value => <div>User name: {value.name}.</div>} </UserConsumer> </> ); }
خروجی برنامه پس از این تغییرات به صورت زیر است:
ساده سازی دسترسی به UserConsumer توسط useContext Hook
نحوهی تعریف یک Provider و محصور سازی فرزندانی که باید از آن ارثبری کنند، در بین کامپوننتهای کلاسی و تابعی، یکی است. اما در کامپوننتهای تابعی حداقل میتوان نحوهی دسترسی به UserConsumer را به نحو زیر توسط useContext Hook ساده کرد:
import React, { useContext } from "react"; import { UserContext } from "../../contexts/userContext"; export default function Main() { const value = useContext(UserContext); return ( <> <div>User name: {value.name}.</div> </> ); }
مزیت دیگر این روش، ساده سازی کار با چندین شیء Context است. برای مثال اگر دو شیء Context را تعریف کرده باشید، خواندن دو مقدار از آنها، پیشتر چنین شکل تو در تویی را توسط دو Consumer پیدا میکرد:
function HeaderBar() { return ( <CurrentUser.Consumer> {user => <Notifications.Consumer> {notifications => <header> Welcome back, {user.name}! You have {notifications.length} notifications. </header> } } </CurrentUser.Consumer> ); }
function HeaderBar() { const user = useContext(CurrentUser); const notifications = useContext(Notifications); return ( <header> Welcome back, {user.name}! You have {notifications.length} notifications. </header> ); }
ارسال اطلاعات به کامپوننت Context Provider، از طریق کامپوننتهای فرزند
تا اینجا با استفاده از React Context، اطلاعات یک Provider را با فرزندان آن به اشتراک گذاشتیم؛ عکس این عمل نیز میسر است. برای اینکار، همانند تمام کامپوننتهای دیگری که برای ارسال اطلاعات به فراخوان خود از طریق رخدادها عمل میکنند، میتوان یک متد رویدادگردان را در کامپوننت والد، به استفاده کنندهی از Context ارسال کرد:
import "../../App.css"; import React, { Component } from "react"; import { UserProvider } from "../../contexts/userContext"; import Main from "./Main2"; class App extends Component { state = { user: { name: "User 1" } }; componentDidMount() { // get user from the server or local storage and then set the currently logged in user to the this.state } logout = () => { console.log("logout"); this.setState({ user: {} }); }; render() { const contextValue = { user: this.state.user, logoutUser: this.logout }; return ( <> <h1>App Class</h1> <UserProvider value={contextValue}> <Main /> </UserProvider> </> ); } } export default App;
اکنون تمام استفاده کنندههای از این UserProvider میتوانند با فراخوانی متد منتسب به logout، سبب پاک شدن اطلاعات کاربر موجود در state کامپوننت App، به روز رسانی state و در نتیجهی آن، رندر مجدد کامپوننت و ارائهی یک UserProvider جدید، با اطلاعاتی جدید به فرزندان آن شوند:
import React, { useContext } from "react"; import { UserContext } from "../../contexts/userContext"; export default function Main() { const { user, logoutUser } = useContext(UserContext); return ( <> <div>User name: {user.name}.</div> <button type="button" className="btn btn-primary" onClick={logoutUser}> Logout user </button> </> ); }
روش انجام اینکار بدون استفاده از useContext را نیز در ادامه مشاهده میکنید که در ابتدا نیاز به تعریف تابعی را دارد که همان خواص استخراجی را دریافت میکند. سپس باید بر اساس آنها، المانهای مدنظر نمایش نام کاربر و دکمهی خروج او را بازگشت داد:
import React from "react"; import { UserConsumer } from "../../contexts/userContext"; export default function Main(props) { return ( <> <UserConsumer> {({ user, logoutUser }) => ( <> <div>User name: {user.name}.</div> <button type="button" className="btn btn-primary" onClick={logoutUser} > Logout user </button> </> )} </UserConsumer> </> ); }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-30-part-04.zip
یک نکتهی تکمیلی: روش طراحی binding دو طرفه در Blazor SSR
در نکتهی قبل عنوان شد که مقدمات طراحی binding دو طرفه، داشتن حداقل سه خاصیت زیر در یک کامپوننت سفارشی است:
[Parameter] public T? Value { set; get; } [Parameter] public EventCallback<T?> ValueChanged { get; set; } [Parameter] public Expression<Func<T?>> ValueExpression { get; set; } = default!;
اگر این خواص را به کامپوننتهای توکار خود Blazor متصل کنیم (مانند InputBox آن و مابقی آنها)، نیازی به کدنویسی بیشتری ندارند و کار میکنند. اما اگر قرار است از یک input سادهی Html ای استفاده کنیم، نیاز است ValueChanged آنرا اینبار در متد OnInitialized فراخوانی کنیم؛ چون در زمان post-back به سرور است که مقدار آن در اختیار مصرف کنندهی کامپوننت قرار میگیرد. این مورد، مهمترین تفاوت نحوهی طراحی binding دوطرفه در Blazor SSR با مابقی حالات و نگارشهای Blazor است.
بررسی وقوع post-back به سرور به دو روش زیر میسر است:
الف) بررسی کنیم که آیا HttpPost ای رخدادهاست؟ سپس در همین لحظه، متد ValueChanged.InvokeAsync را فراخوانی کنیم:
[CascadingParameter] internal HttpContext HttpContext { set; get; } = null!; protected override void OnInitialized() { base.OnInitialized(); if (HttpContext.IsPostRequest()) { SetValueCallback(Value); } } private void SetValueCallback(string value) { if (!ValueChanged.HasDelegate) { return; } _ = ValueChanged.InvokeAsync(value); }
در این مثال نحوهی فعالسازی ارسال اطلاعات از یک کامپوننت سفارشی را به مصرف کنندهی آن ملاحظه میکنید. اینکار در قسمت OnInitialized و فقط در صورت ارسال اطلاعات به سمت سرور، فعال خواهد شد.
ب) میتوان در قسمت OnInitialized، بررسی کرد که آیا درخواست جاری به همراه اطلاعات یک فرم ارسال شدهی به سمت سرور است یا خیر؟ روش کار به صورت زیر است:
protected override void OnInitialized() { base.OnInitialized(); if (HttpContext.Request.HasFormContentType && HttpContext.Request.Form.TryGetValue(ValueField.HtmlFieldName, out var data)) { SetValueCallback(data.ToString()); } }
در اینجا از ValueField.HtmlFieldName که در نکتهی قبلی معرفی BlazorHtmlField به آن اشاره شد، جهت یافتن نام واقعی فیلد ورودی، استفاده شدهاست.
پیشنیازها
«بررسی روش آپلود فایلها در ASP.NET Core»
«ارسال فایل و تصویر به همراه دادههای دیگر از طریق jQuery Ajax »
- در مطلب اول، روش دریافت فایلها از کلاینت، در سمت سرور و ذخیره سازی آنها در یک برنامهی ASP.NET Core بررسی شدهاست که کلیات آن در اینجا نیز صادق است.
- در مطلب دوم، روش کار با FormData استاندارد بررسی شدهاست. هرچند در مطلب جاری از jQuery استفاده نمیشود، اما نکات نحوهی کار با شیء FormData استاندارد، در اینجا نیز یکی است.
برپایی پروژههای مورد نیاز
ابتدا یک پوشهی جدید مانند UploadFilesSample را ایجاد کرده و در داخل آن دستور زیر را اجرا میکنیم:
dotnet new react
سپس در این پوشه، پوشهی ClientApp پیشفرض آنرا حذف میکنیم؛ چون کمی قدیمی است. همچنین فایلهای کنترلر و سرویس آب و هوای پیشفرض آنرا به همراه پوشهی صفحات Razor آن، حذف و پوشهی خالی wwwroot را نیز به آن اضافه میکنیم.
همچنین بجای تنظیم پیش فرض زیر در فایل کلاس آغازین برنامه:
spa.UseReactDevelopmentServer(npmScript: "start");
spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
اکنون در ریشهی پروژهی ASP.NET Core ایجاد شده، دستور زیر را صادر میکنیم تا پروژهی کلاینت React را با فرمت جدید آن ایجاد کند:
> create-react-app clientapp
> cd clientapp > npm install --save bootstrap axios react-toastify
- برای استفاده از شیوهنامههای بوت استرپ، بستهی bootstrap نیز در اینجا نصب میشود که برای افزودن فایل bootstrap.css آن به پروژهی React خود، ابتدای فایل clientapp\src\index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
- برای نمایش پیامهای برنامه از کامپوننت react-toastify استفاده میکنیم که پس از نصب آن، با مراجعه به فایل app.js نیاز است importهای لازم آنرا اضافه کنیم:
import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css";
render() { return ( <React.Fragment> <ToastContainer />
ایجاد کامپوننت React فرم ارسال فایلها به سمت سرور
پس از این مقدمات، فایل جدید clientapp\src\components\UploadFileSimple.jsx را ایجاد کرده و به صورت زیر تکمیل میکنیم:
import React, { useState } from "react"; import axios from "axios"; import { toast } from "react-toastify"; export default function UploadFileSimple() { const [description, setDescription] = useState(""); const [selectedFile1, setSelectedFile1] = useState(); const [selectedFile2, setSelectedFile2] = useState(); return ( <form> <fieldset className="form-group"> <legend>Support Form</legend> <div className="form-group row"> <label className="form-control-label" htmlFor="description"> Description </label> <input type="text" className="form-control" name="description" onChange={event => setDescription(event.target.value)} value={description} /> </div> <div className="form-group row"> <label className="form-control-label" htmlFor="file1"> File 1 </label> <input type="file" className="form-control" name="file1" onChange={event => setSelectedFile1(event.target.files[0])} /> </div> <div className="form-group row"> <label className="form-control-label" htmlFor="file2"> File 2 </label> <input type="file" className="form-control" name="file2" onChange={event => setSelectedFile2(event.target.files[0])} /> </div> <div className="form-group row"> <button className="btn btn-primary" type="submit" > Submit </button> </div> </fieldset> </form> ); }
- توسط آن یک textbox به همراه دو فیلد ارسال فایل، به فرم اضافه شدهاند.
- مرحلهی بعد، دسترسی به فایلهای انتخابی کاربر و همچنین مقدار توضیحات وارد شدهاست. به همین جهت با استفاده از useState Hook، روش دریافت و تنظیم این مقادیر را مشخص کردهایم:
const [description, setDescription] = useState(""); const [selectedFile1, setSelectedFile1] = useState(); const [selectedFile2, setSelectedFile2] = useState();
- پس از طراحی state این فرم، مرحلهی بعدی، استفاده از متدهای set تمام useStateهای فوق است. برای مثال در مورد یک textbox معمولی، میتوان آنرا به صورت inline تعریف کرد و با هر بار تغییری در محتوای آن، این رخداد را به متد setDescription ارسال نمود تا مقدار وارد شده را به متغیر حالت description انتساب دهد:
<input type="text" className="form-control" name="description" onChange={event => setDescription(event.target.value)} value={description} />
<input type="file" className="form-control" name="file1" onChange={event => setSelectedFile1(event.target.files[0])} />
تشکیل مدل ارسال دادهها به سمت سرور
در فرمهای معمولی، عموما دادهها به صورت یک شیء JSON به سمت سرور ارسال میشوند؛ اما در اینجا وضع متفاوت است و به همراه توضیحات وارد شده، دو فایل باینری نیز وجود دارند.
در حالت ارسال متداول فرمهایی که به همراه المانهای دریافت فایل هستند، ابتدا یک ویژگی enctype با مقدار multipart/form-data به المان فرم اضافه میشود و سپس این فرم به سادگی قابلیت post-back به سمت سرور را پیدا میکند:
<form enctype="multipart/form-data" action="/upload" method="post"> <input id="file-input" type="file" /> </form>
let file = document.getElementById("file-input").files[0]; let formData = new FormData(); formData.append("file", file); fetch('/upload/image', {method: "POST", body: formData});
در یک برنامهی React نیز باید دقیقا چنین مراحلی طی شوند. تا اینجا کار دسترسی به مقدار files[0] و تشکیل متغیرهای حالت فرم را انجام دادهایم. در مرحلهی بعد، شیء FormData را تشکیل خواهیم داد:
// ... export default function UploadFileSimple() { // ... const handleSubmit = async event => { event.preventDefault(); const formData = new FormData(); formData.append("description", description); formData.append("file1", selectedFile1); formData.append("file2", selectedFile2); toast.success("Form has been submitted successfully!"); setDescription(""); }; return ( <form onSubmit={handleSubmit}> </form> ); }
ارسال مدل دادههای فرم React به سمت سرور
پس از تشکیل شیء FormData در متد مدیریت کنندهی handleSubmit، اکنون با استفاده از کتابخانهی axios، کار ارسال این اطلاعات را به سمت سرور انجام خواهیم داد:
// ... export default function UploadFileSimple() { const apiUrl = "https://localhost:5001/api/SimpleUpload/SaveTicket"; // ... const [isUploading, setIsUploading] = useState(false); const handleSubmit = async event => { event.preventDefault(); const formData = new FormData(); formData.append("description", description); formData.append("file1", selectedFile1); formData.append("file2", selectedFile2); try { setIsUploading(true); const { data } = await axios.post(apiUrl, formData, { headers: { "Content-Type": "multipart/form-data" }} }); toast.success("Form has been submitted successfully!"); console.log("uploadResult", data); setIsUploading(false); setDescription(""); } catch (error) { setIsUploading(false); toast.error(error); } }; return ( // ... ); }
در قطعه کد فوق، متغیر جدید حالت isLoading را نیز مشاهده میکنید. از آن میتوان برای فعال و غیرفعال کردن دکمهی submit فرم در زمان ارسال اطلاعات به سمت سرور، استفاده کرد:
<button disabled={ isUploading } className="btn btn-primary" type="submit" > Submit </button>
اعتبارسنجی سمت کلاینت فایلهای ارسالی به سمت سرور
در اینجا شاید نیاز باشد نوع و یا اندازهی فایلهای انتخابی توسط کاربر را تعیین اعتبار کرد. به همین جهت متدی را برای اینکار به صورت زیر تهیه میکنیم:
const isFileValid = selectedFile => { if (!selectedFile) { // toast.error("Please select a file."); return false; } const allowedMimeTypes = [ "image/png", "image/jpeg", "image/gif", "image/svg+xml" ]; if (!allowedMimeTypes.includes(selectedFile.type)) { toast.error(`Invalid file type: ${selectedFile.type}`); return false; } const maxFileSize = 1024 * 500; const fileSize = selectedFile.size; if (fileSize > maxFileSize) { toast.error( `File size ${(fileSize / 1024).toFixed( 2 )} KB must be less than ${maxFileSize / 1024} KB` ); return false; } return true; };
اکنون برای استفادهی از این متد دو راه وجود دارد:
الف) استفاده از آن در متد مدیریت کنندهی submit اطلاعات:
const handleSubmit = async event => { event.preventDefault(); if (!isFileValid(selectedFile1) || !isFileValid(selectedFile2)) { return; }
ب) استفادهی از آن جهت غیرفعال کردن دکمهی submit:
<button disabled={ isUploading || !isFileValid(selectedFile1) || !isFileValid(selectedFile2) } className="btn btn-primary" type="submit" > Submit </button>
نمایش درصد پیشرفت آپلود فایلها
کتابخانهی axios، امکان دسترسی به میزان اطلاعات آپلود شدهی به سمت سرور را به صورت یک رخداد فراهم کردهاست که در ادامه از آن برای نمایش درصد پیشرفت آپلود فایلها استفاده میکنیم:
const startTime = Date.now(); const { data } = await axios.post(apiUrl, formData, { headers: { "Content-Type": "multipart/form-data" }, onUploadProgress: progressEvent => { const { loaded, total } = progressEvent; const timeElapsed = Date.now() - startTime; const uploadSpeed = loaded / (timeElapsed / 1000); setUploadProgress({ queueProgress: Math.round((loaded / total) * 100), uploadTimeRemaining: Math.ceil((total - loaded) / uploadSpeed), uploadTimeElapsed: Math.ceil(timeElapsed / 1000), uploadSpeed: (uploadSpeed / 1024).toFixed(2) }); } });
const [uploadProgress, setUploadProgress] = useState({ queueProgress: 0, uploadTimeRemaining: 0, uploadTimeElapsed: 0, uploadSpeed: 0 });
const showUploadProgress = () => { const { queueProgress, uploadTimeRemaining, uploadTimeElapsed, uploadSpeed } = uploadProgress; if (queueProgress <= 0) { return <></>; } return ( <table className="table"> <thead> <tr> <th width="15%">Event</th> <th>Status</th> </tr> </thead> <tbody> <tr> <td> <strong>Elapsed time</strong> </td> <td>{uploadTimeElapsed} second(s)</td> </tr> <tr> <td> <strong>Remaining time</strong> </td> <td>{uploadTimeRemaining} second(s)</td> </tr> <tr> <td> <strong>Upload speed</strong> </td> <td>{uploadSpeed} KB/s</td> </tr> <tr> <td> <strong>Queue progress</strong> </td> <td> <div className="progress-bar progress-bar-info progress-bar-striped" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow={queueProgress} style={{ width: queueProgress + "%" }} > {queueProgress}% </div> </td> </tr> </tbody> </table> ); };
{showUploadProgress()}
در اینجا از کامپوننت progress-bar خود بوت استرپ برای نمایش درصد آپلود فایلها استفاده شدهاست. اگر style آنرا هر بار با مقدار جدید queueProgress به روز رسانی کنیم، سبب نمایش پویای این progress-bar خواهد شد.
یک نکته: اگر میخواهید درصد پیشرفت آپلود را در حالت آزمایش local بهتر مشاهده کنید، دربرگهی network، سرعت را بر روی 3G تنظیم کنید (مانند تصویر ابتدای بحث)؛ در غیراینصورت همان ابتدای کار به علت بالا بودن سرعت ارسال فایلها، 100 درصد را مشاهده خواهید کرد.
دریافت فرم React درخواست پشتیبانی، در سمت سرور و ذخیرهی فایلهای آن
بر اساس نحوهی تشکیل FormData سمت کلاینت:
const formData = new FormData(); formData.append("description", description); formData.append("file1", selectedFile1); formData.append("file2", selectedFile2);
using Microsoft.AspNetCore.Http; namespace UploadFilesSample.Models { public class Ticket { public int Id { set; get; } public string Description { set; get; } public IFormFile File1 { set; get; } public IFormFile File2 { set; get; } } }
پس از آن کنترلر ذخیره سازی اطلاعات Ticket را مشاهده میکنید:
using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using UploadFilesSample.Models; namespace UploadFilesSample.Controllers { [Route("api/[controller]")] [ApiController] public class SimpleUploadController : Controller { private readonly IWebHostEnvironment _environment; public SimpleUploadController(IWebHostEnvironment environment) { _environment = environment; } [HttpPost("[action]")] public async Task<IActionResult> SaveTicket([FromForm]Ticket ticket) { var file1Path = await saveFileAsync(ticket.File1); var file2Path = await saveFileAsync(ticket.File2); //TODO: save the ticket ... get id return Created("", new { id = 1001 }); } private async Task<string> saveFileAsync(IFormFile file) { const string uploadsFolder = "uploads"; var uploadsRootFolder = Path.Combine(_environment.WebRootPath, "uploads"); if (!Directory.Exists(uploadsRootFolder)) { Directory.CreateDirectory(uploadsRootFolder); } //TODO: Do security checks ...! if (file == null || file.Length == 0) { return string.Empty; } var filePath = Path.Combine(uploadsRootFolder, file.FileName); using (var fileStream = new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(fileStream); } return $"/{uploadsFolder}/{file.Name}"; } } }
- تزریق IWebHostEnvironment در سازندهی کلاس کنترلر، سبب میشود تا از طریق خاصیت WebRootPath آن، به wwwroot دسترسی پیدا کنیم و فایلهای نهایی را در آنجا ذخیره سازی کنیم.
- همانطور که ملاحظه میکنید، هنوز هم model binding کار کرده و میتوان شیء Ticket را به نحو متداولی دریافت کرد:
public async Task<IActionResult> SaveTicket([FromForm]Ticket ticket)
const { data } = await axios.post(apiUrl, formData, { headers: { "Content-Type": "multipart/form-data" }} });
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: UploadFilesSample.zip
برای اجرای آن، پس از صدور فرمان dotnet restore که سبب بازیابی وابستگیهای سمت کلاینت نیز میشود، ابتدا به پوشهی clientapp مراجعه کرده و فایل run.cmd را اجرا کنید. با اینکار react development server بر روی پورت 3000 شروع به کار میکند. سپس به پوشهی اصلی برنامهی ASP.NET Core بازگشت شده و فایل dotnet_run.bat را اجرا کنید. این اجرا سبب راه اندازی وب سرور برنامه و همچنین ارائهی برنامهی React بر روی پورت 5001 میشود.
تعریف متدها در برنامه نویسی:
متدها جزء اولین چیزهایی هستند که در هنگام شروع برنامه نویسی در هر یک از زبانهای برنامه نویسی، برنامه نویس با آنها آشنا میشود. بنابراین متدها به عنوان اصلیترین Building Block ها در زبانهای برنامه نویسی دارای اهمیت بسیار زیادی میباشند. متدها اولین جاهایی هستند که ما میتوانیم کار خودمان را از آنها شروع کنیم و به سوی هدف خود حرکت کنیم.
همان طور که میدانید شما در هنگام کد نویسی یکسری عملکردها را با ارسال یکسری پارمترها به متدها و فراخوانی آنها، بر عهدهی یک متد خاص میگذارید. بعد از فراخوانی انتظار دارید که متد، یک نوع دادهی خاص را برگرداند یا اینکه انتظار ندارید هیچ مقداری را برگرداند. همچنین احتمال دارد در هنگام اجرای متدی، یکسری خطاها رخ دهند و برنامه نویس باید سعی کند همهی آنها را به درستی مدیریت کند. اما آیا به نظر شما مواردی که در مورد متدها دارای اهمیت میباشند، فقط اینها هستند؟
بسیاری از برنامه نویسان انتظاری که از متدها دارند فقط در همین حد میباشد و بیشتر از این درگیر هیچ مسئلهی دیگری نمیشوند. اما آیا فقط در نظر گرفتن این مسایل در رسیدن به یک کد خوش ساخت، قابل توسعه و بدون پیچیدگی کافی است؟
متدها علاوه بر ویژگیهای ذکر شدهی در بالا که بیشتر بر ویژگیهای ذاتی و عملکردی آن تمرکز داشت، باید داری یکسری ویژگیهای دیگر نیز باشند، متدها باید Clean ، Testable و Predictable باشند. هر کدام از ویژگیهای مذکور توسط این پارامترها تشریح میشوند.
در ذیل ویژگیهای مذکور در شکل بالا را تشریح خواهیم کرد.
Clear Purpose :
· یک متد یک کار را انجام میدهد و همچنین آن کار را نیز به خوبی انجام میدهد.
· متد به راحتی قابل درک میباشد.
· میزان خطاها را به شدت کاهش میدهد.
· دیباگ کردن را در صورت وجود هر خطایی سادهتر میکند.
· قابلیت توسعه را افزایش میدهد.
· نوشتن تست برای این نوع متدها به دلیل اینکه فقط بر روی یک هدف خاص تمرکز دارند ساده است.
Good Name :
· نام متد عمکرد آن را به روشنی بیان میکند
Focused Code :
· تمام کد نوشته شدهی در متد فقط بر روی یک هدف تمرکز دارند.
· خوانایی کد بالا است و میزان توضیحاتی که برای کد نوشته میشود در کمترین حد ممکن است.
· متد دارای تاثیرات ناخواستهای بر سایر قسمتهای نرم افزار نمیباشد. این مسئله به معنی است که این نوع متدها شامل کدهایی که کارهای ناخواسته ای را انجام میدهند نمیباشد. برای مثال متدی که برای واکشی اطلاعات مشتریان استفاده میشود هیچگونه عملیاتی را که برای ثبت اطلاعات مشتریان انجام میشود، انجام نمیدهد.
Short Length :
· تعداد خطوط کد مربوط به متد کم میباشد. این مسئله خود باعث کاهش باگهای احتمالی در یک متد میشود.
Automated Code Test :
· متد این قابلیت را دارد که توسط زیر ساختهای تست، تست شود که این مسئله خود باعث افزایش کیفیت کد میشود.
Predictable Result :
· متد دارای یک نتیجهی قابل پیش بینی میباشد.
در ادامه سعی میکنیم با ذکر یک مثال، مواردی را که ذکر شد بیشتر توضیح دهیم و دیدگاه کاربردی آن را بررسی کنیم.
مثالی از دنیای واقعی:
مثال زیر فرآیند مربوط به دریافت سفارش از مشتری را به صورت کدی محاورهای نمایش میدهد. در این مثال سعی شده مشکلات و راه حلهای پیشنهادی Defensive Code با تمرکز بر مواردی که در قسمت قبل ذکر شد به صورت کامل تشریح شود. اکثر ما به عنوان برنامه نویس، با مواردی مانند شکل ذیل مواجه شدهایم. در این حالت در هنگام طراحی نرم افزار برنامه نویس مستقیما وارد رویداد مربوط به کلیک دکمهی ثبت اطلاعات سفارش میشود و به صورت کاملا ناباورانه و با پشتکاری مثال زدنی شروع به کد زدن میکند. برنامه نویس تمامی منطق دریافت اطلاعات از کاربر و ثبت مشترک، ایجاد درخواست برای مشترک، مراحل دریافت کالا از انبار، پرداخت، ارسال ایمیل به مشتری و سایر عملیاتهای دیگر را در این متد و پشت سر هم مینویسد:
این روش کد نویسی روشی است که اکثر برنامه نویسان با آن آشنایی دارند. اولین مشکلی که این روش دارد این است که این کد Clean نمیباشد. قابلیت توسعه و نگهداری این کد بسیار پایین میباشد و به اصطلاح یک کد کاملا باگ خیز میباشد. حال نوبت این رسیده که این کد را از نظر پارامترهایی که در بالا ذکر شد بررسی کنیم.
Clear Purpose :
آیا این متد دارای یک هدف مشخص و معین است؟ بیایید بررسی کنیم، ایجاد مشتری، ایجاد سفارش، ارسال درخواست به انبار، انجام عملیات پرداخت و ارسال رسید. همهی این کارها توسط این متد انجام میشود. خودتان در مورد تبعات این روش کد نویسی قضاوت کنید.
Clear Name :
به نظر شما چگونه میتوان یک اسم مناسب برای این متد انتخاب کرد که عملکرد آن را به درستی بیان کند. هر متد به یک نام مناسب نیاز دارد که این مسئله خود قابلیت توسعه و نگهداری کد را افزایش میدهد. این نام میتواند اطلاعات کاملی را در مورد متد ارائه دهد و عملکرد کلی آن را بیان نماید. هدف متد باید از طریق نام متد بیان شود و هنگامیکه شما نتوانید برای متد مد نظر یک نام را انتخاب کنید، بنابراین این متد دارای هدفی مشخص نمیباشد.
Focused Code :
متد باید کاری را انجام دهد که نام آن بیان میکند و تمام کدهای متد باید حتما بر روی آن هدف تمرکز کنند.
Short Length :
متد باید دارای طول کمی باشد. برای مثال طول کد نباید از اندازهی صفحه نمایش بیشتر باشد. به عبارتی دیگر، کد متد نباید اسکرول بخورد. بنابراین باید سعی شود کدهای اضافی از متد حذف شوند تا با این کار پیچیدگی کد هم به کمترین میزان برسد.
Automated Code Test :
آیا این متد به وسیلهی Automated Code Test می تواند تست شود؟ چند نکته در مورد این کد وجود دارد که توانایی Automated Code Test را از این کد میگیرد. اولین مسئله این است که در این مثال منطق یا همان Business برنامه با UI تلفیق شدهاست. برای رفع این مشکل باید منطق برنامه را در یک پروژهی مجزا از نوع Class Library قرار داد. مسئلهی دیگر این است که این متد برای تست شدن بسیار طولانی میباشد و باید به یکسری اجزای کوچکتر و منطقیتر شکسته شود و هر متد باید یک هدف و عملکرد روشن را داشته باشد.
در قسمت بعدی راهکارهایی برای Refactor کردن کد بر اساس اصول ذکر شده ارائه خواهد شد.
یکی از تجربیاتی که من در زمینه طراحی و پیاده سازی سیستمهای توزیع شده داشتهام «سیستم آمارنامه فرآوردههای دارویی کشور» است. هدف این سیستم، تامین کردن آماری از زنجیره تامین فرآوردههای دارویی کشور است و در آن همه چیز در قالب رخدادهایی که در این زنجیره اتفاق میافتند، بوجود میآید. یعنی ما باید تمام رخدادها را از لحظهای که یک تولید کننده یا وارد کننده، فرآورده را وارد این زنجیره میکند، تا لحظهای که فرآورده توسط داروخانه به مشتری تحویل داده میشود و از زنجیره خارج میشود، ثبت کنیم و در مرحله بعد گزارشات کاملی را از اطلاعات ثبت شده، در اختیار تمام تولید کنندگان، وارد کنندگان، توزیع کنندگان و شعب آنها، داروخانهها، یکسری از ارگانهای دولتی، دانشگاهها و عموم جامعه قرار بدهیم.
نمایی از زنجیره تامین فرآوردههای دارویی و نحوه فراخوانی سرویس آمارنامه
در این سیستم چالشهای بسیار مهمی وجود دارند که پس از بررسیهای انجام شده، برای هر یک راه حلی ارائه خواهد شد:
چالش اول: در دسترس بودن سیستم
در دسترس بودن این سرویس بسیار حیاتی است. یعنی با از دسترس خارج شدن این سرویس، قسمتی از دادههای اصلی خود را از دست میدهیم؛ که باعث میشود آمار ارائه شده درست نباشد.
ارائه راه حل:
بدلیل اینکه احتمال از دسترس خارج شدن یک سرور همیشه وجود دارد، این چالش به تنهایی میتواند دلیل محکمی برای پیاده سازی سیستم بصورت توزیع شده باشد. برای حل این مشکل میتوانیم از روش Active/Standby استفاده کنیم. به این صورت که چند کپی از سرویس روی چند سرور داشته باشیم که هر لحظه یکی از این سرورها فعال باشد. با از دسترس خارج شدن سرور Active، یکی از سرورهای Standby فعال شود و درخواستهای جدید برای این سرور ارسال شوند.
این روش تنها قابلیت در دسترس بودن سیستم را افزایش میدهد و هیچ تاثیری روی کارآیی سیستم ندارد.
برای رفع مشکل فوق، از روش Replicate روی یک یا چند Cluster استفاده میکنیم. یعنی چند کپی از سرویس، روی چند سرور داشته باشیم؛ به این صورت که همه آنها فعال باشند. درخواستها با الگوریتمی که انتخاب میکنیم، از طریق Load Balancer بین این Nodeها پخش میشوند. با این روش، هم کارآیی سیستم بالا میرود و هم همیشه Nodeهایی وجود دارند که جای Nodeهای از دسترس خارج شده را بگیرند.
این روش کارآیی سیستم را افزایش چشمگیری میدهد. اما بدلیل اینکه یک Load Balancer داریم، در صورتیکه به هر دلیلی Load balancer از دسترس خارج شود، کل سیستم از دسترس خارج میشود.
برای رفع مشکل فوق بصورت ترکیبی، از هر دو روش در قسمتهای مختلف استفاده میکنیم که در این روش احتمال از دسترس خارج شدن سیستم به حداقل ممکن میرسد و کارآیی سیستم نیز به حداکثر ممکن میرسد.
(در هر صورت بهترین راه حل برای این چالش، استفاده از سیستمهای توزیع شده است.)
چالش دوم: تعداد کاربران و تعداد درخواست بسیار زیاد و همیشه رو به افزایشند
کاربران این سیستم شامل تمام داروخانههای کشور، تمام توزیع کنندگان و شعب آنها، تمام تولید کنندگان، تمام وارد کنندگان، دانشگاههای مرتبط، یکسری از ارگانهای دولتی و عموم جامعه هستند. یعنی سیستم شامل تعداد کاربران بسیار زیادی است که چیزی در حدود 15000 کاربر از این مجموعه وظیفه دارند بصورت فعال و متناوب با این سیستم کار کنند. کاربران این سیستم همیشه رو به افزایشند.
به نسبت تعدادکاربران و رو به افزایش بودن آنها، درخواست از این سیستم، هیچگاه قطع نمیشود و همیشه رو به افزایش است. با رخ دادن هر Event، یک درخواست برای سیستم ارسال میشود. بطور مثال تنها در آخرین مرحله به ازای هر رخداد داروخانه، درخواستی برای سیستم ارسال میشود (تنها یکی از رخدادهای داروخانه، رخداد فروش است که با ارائه هر نسخه توسط مشتری اتفاق میافتد). با توجه به اینکه در کشور چیزی در حدود 12000 داروخانه وجود دارند، سیستم باید توانایی پاسخ دادن به 12000 درخواست بصورت همزمان و متناوب، آن هم فقط برای رخداد فروش داروخانهها را داشته باشد.
ارائه راه حل:
بدلیل تعداد بسیار زیاد درخواستها و بالا رفتن این تعداد، بصورت لحظهای و حیاتی بودن دسترسی به این سیستم، سیستم باید قابلیت این را داشته باشد که بدون از دسترس خارج شدن، اولا درخواستهای جاری را پاسخ دهد، دوما همیشه آمادگی لازم را برای افزایش تعداد درخواستها، داشته باشد. یعنی به هیچ وجه Scale-up بهتنهایی پاسخگوی نیاز ما نیست و برای رفع این مشکل باید از Scale-out کمک بگیریم. یعنی با افزایش تعداد درخواستها، بدون از دسترس خارج شدن سیستم و با کمترین هزینه و پیچیدگی، Nodeهایی به سیستم اضافه کنیم که قسمتی از بار پردازشی در آنها انجام شود.
در این روش ما میتوانیم به راحتی و با کمترین هزینه، با افزایش تعداد درخواست، Nodeهایی را به Cluster اضافه کنیم تا بار پردازشی اضافی در آنها رفع شود. همچنین برای استفاده بهینه از منابع، با کاهش درخواست، Nodeهایی را از Cluster خارج کنیم. همچنین قابلیت در دسترس بودن این سیستم نیز در بالاترین سطح خود قرار دارد.
چالش سوم: حجم زیاد هر درخواست و زمان زیاد مورد نیاز برای پردازش آن
روال پاسخ دادن به هر درخواست، شامل دریافت درخواست، گرفتن Log از درخواست، اعمال دسترسیهای ارسال کننده درخواست، اعتبارسنجی درخواست، پردازش درخواست، ذخیره آن و پاسخ به کاربر است و بدلیل اینکه هر رخداد میتواند شامل اطلاعات بسیار زیادی باشد، انجام همه این اعمال، زمان زیادی را میطلبد. همچنین با توجه به تعداد کاربران، تعداد درخواست و حجم دادهای که باید ذخیره کنیم - در صورتی که هر درخواست نیز بخواهد در مدت زمان زیادی پردازش شود - سیستم با حجم بسیار زیادی از درخواست مواجه است که هر یک زمانی زیادی را نیز برای پردازش نیاز دارد.
ارائه راه حل:
در صورت ارائه راه حل نادرست برای حل این چالش، با توجه به تعداد درخواست و دادههایی که در سیستم ذخیره شدهاند، این چالش میتواند برای سیستم، مشکلات بسیار زیادی را ایجاد کند. به همین دلیل باید این پردازش بزرگ را به پردازشهای کوچکتری که قابلیت Concurrency را با کمترین میزان تاخیر دارند و هدف همه آنها پاسخ دادن به کاربر است، تبدیل کنیم.
با تقسیم بندی وظایف و قرار دادن هریک از این وظایف در سخت افزارهای متفاوت، سیستم این قابلیت را دارد که برای کاربر همیشه در دسترس باشد. در کمترین زمان بیشترین تعداد درخواست را بصورت همزمان و با کمترین تاخیر پردازش کند و با افزایش درخواستها، برای هر قسمت میتوانیم تعداد Node موجود در آن قسمت را افزایش دهیم.
چالش چهارم: حجم بسیار زیاد و رو به افزایش دادههای سیستم
دادههای این سیستم ذاتا همیشه و در هر شرایطی رو به افزایش هستند و هیچگاه جریان داده، در این سیستم قطع نمیشود. با توجه به تعداد کاربران، تعداد درخواست و نوع داده، ما با حجم دادهی بسیار زیادی روبرو هستیم که پایانی ندارند.
ارائه راه حل:
با توجه به حیاتی بودن دسترسی به سیستم و سایر چالشهایی که در قسمتهای قبلی ذکر شد، در صورتیکه حتی تمام قسمتهای قبل را بهدرستی طراحی و پیاده سازی کنیم، اگر برای این چالش راه حل درستی را ارائه ندهیم، تمامی راه حلهای قبلی که ارائه کردیم، بی فایده میباشند. چون با از دسترس خارج شدن Database، کل سیستم از دسترس خارج میشود.
برای رفع این مشکل واقعا نمیتوان از یک سخت افزار استفاده کرد؛ چون دقیقا شبیه به این است که تعداد خودروهای بسیار زیادی که از طریق یک بزرگراه چند بانده حرکت میکنند و جریان آنها هیچگاه قطع نمیشود، در انتهای مسیر وارد یک پارکینگ شوند. یعنی در انتها باید وارد یک پارکینگ شوند که در هر لحظه ممکن است ظرفیت آن پر شود. گذشته از این برای رفتن به این پارکینگ باید وارد یک صف شوند که زمان انتظار آنها را افزایش میدهد. یک سخت افزار همیشه قابلیت از دسترس خارج شدن را دارد. با جریان داده افزایشی، همیشه احتمال پر شدن حافظهاش وجود دارد. گذشته از همه اینها به احتمال زیاد قادر به پاسخ دادن به تعداد درخواستهای بسیار زیادی که هر لحظه ممکن است تعداد آنها بیشتر شود را نیز نداشته باشد.
نتیجه گیری این است که تقریبا تمام چالشهایی که برای سرویس وجود داشت، برای Database نیز وجود دارد. به همین دلیل باید Database نیز بصورت توزیع شده پیاده سازی شود:
این طراحی تقریبا تمامی قابلیتهای طراحی سرویسمان را دارد. یعنی با افزایش تعداد درخواست، یا کم شدن فضای ذخیره سازی در هر یک از Nodeها، ما این قابلیت را داریم که Nodeهایی را به آن اضافه کنیم. همچنین بدلیل اینکه دادههای ما در دو یا چند Node کپی شدهاند، با از دسترس خارج شدن هر Node همیشه Nodeهایی وجود دارند که جای Node معیوب را بگیرند؛ تا زمانیکه Node معیوب دوباره به سیستم بازگردد.
همانطور که دیدید، هر یک از چالشهای ذکر شده به تنهایی قابلیت این را دارند که سیستم خود را بهصورت توزیع شده پیاده سازی کنید. اما نکته بسیار مهمی که باید همیشه در نظر داشته باشید این است که تصمیمات شما همیشه باید با بررسیهای کامل از جنبههای مختلف گرفته شوند. در دنیای واقعی علاوه برفاکتورهایی که هر یک بصورت یک چالش در قسمت بالا ذکر شد، فاکتورهای دیگری نیز وجود دارند که میتوانند عاملی برای انتخاب، یا عدم انتخاب سیستمهای توزیع شده باشند. فاکتورهایی که در ادامه مطلب ذکر میشوند.
مهمترین فاکتورهای انتخاب سیستمهای توزیع شده:
1- هزینه: هزینه میتواند مهمترین فاکتور در انتخاب یک سیستم توزیع شده باشد. هیچ کسی نمیخواهد سیستمی را طراحی کند که هزینه طراحی، پیاده سازی و نگهداری آن بیشتر از سود حاصل از آن باشد. یا کمتر پیش میآید که گروهی تصمیم بگیرند که وقتی که یک نوع طراحی و پیاده سازی با هزینه کمتر جوابگوی نیازهای آنها است، از نوع طراحی و پیاده سازی استفاده کنند که هزینه بیشتری را برای آنها ایجاد میکند؛ حتی در صورتیکه طراحی دوم قابلیتهای بیشتری را نیز ایجاد کند.
2- در دسترس بودن سیستم: گاهی ممکن است یک لحظه از دسترس خارج شدن سیستم، عواقب جبران ناپذیری را برای کل سیستم بهوجود بیاورد. در این حالت بهترین انتخاب، سیستمهای توزیع شده است.
3- تعداد یا نوع کاربران سیستم: تعداد کاربرانی که همیشه رو به افزایشند، میتواند فاکتور بسیار مهمی در انتخاب یک سیستم توزیع شده باشد. اما مشکلی که وجود دارد این است که همیشه در ابتدای طراحی این تعداد مشخص نیست. گاهی نیاز است نوع طراحی خود را با توجه به نوع کاربران سیستم انتخاب کنید. بطور مثال سیستم شما نیازهای کاربران یک مکان یا سازمان خاص را رفع میکند، یا نیازهای یک جامعه را رفع میکند. در صورتیکه سیستم شما نیاز کاربران یک محیط بزرگ را رفع کند، همیشه باید منتظر بالا رفتن میزان کاربران سیستم نیز باشید.
4- تعداد درخواستهای از سیستم: تعداد درخواستها در اکثر موارد وابستگی بسیار زیادی به تعداد یا نوع کاربران دارد. پوشش دادن تعداد زیاد درخواست، بصورت متناوب و رو به افزایش میتواند فاکتور بسیار مهمی در انتخاب یک سیستم توزیع شده باشد.
5- نوع و حجم عملیاتی که انجام میدهیم: برخی عملیات ممکن است زمان بسیار زیادی برای اجرا نیاز داشته باشند که میتواند روی سیستم ما تاثیر بسیار زیادی بگذارند. برای افزایش کارآیی و پردازش تعداد بیشتر درخواستها، گاهی بهتر است یک عملیات را تبدیل به عملیاتی کوچکتر کرد و هرکدام از این عملیات کوچکتر را در یک سخت افزار جداگانه اجرا کرد.
6- نوع و حجم دادههایی که نیاز به ذخیره شدن دارند: نوع دادههایی که ذاتا همیشه رو به افزایشند میتواند فاکتور بسیار مهمی در انتخاب سیستمهای توزیع شده باشد. البته این مورد نیز همیشه از ابتدای طراحی مشخص نیست. نوع کاربران شما میتوانند کمک بسیار بزرگی در انتخاب این فاکتور داشته باشند.
7- کارآیی: با یک طراحی و تقسیم بندی درست در قسمتهای مختلف سیستم میتوان حجم و تعداد بسیار زیادی از پردازشها را بصورت همزمان اجرا کرد. البته کاملا بصورت انعطاف پذیر؛ به صورتیکه با بیشتر شدن تعداد و حجم پردازش، سیستم بدون از دسترس خارج شدن، قادر به پوشش دادن آنها باشد.
8- امنیت: پردازش شما میتواند تقسیم بندی شود. بصورتیکه هر قسمت در سرور جداگانهای که از قبل مشخص نیست، اجرا شود. سروری که حتی به اینترنت هم وصل نیست. با طراحی درست میتوان امنیت سیستم را بسیار افزایش داد.
9- موقعیت جغرافیایی کاربران: گاهی بدلیل تعداد زیاد کاربران نیاز است درخواستهای هر کاربر، در نزدیکترین سرور به او پردازش شود. این فاکتور در سیستمهای بسیار بزرگ دلیل بسیار مهمی در انتخاب سیستمهای توزیع شدهاست.
علاوه بر موارد فوق مواردی را مانند Internet of things یا همان IOT که پایه و اساس آن سیستمهای توزیع شدهاست، یا مواردی را مانند Machine learning که میتواند بصورت توزیع شده پیاده سازی شود، نیز در نظر بگیرید.
با در نظر گرفتن تمام موارد فوق و شرایط اختصاصی سیستمی که طراحی میکنید، سعی کنید بهترین انتخاب را انجام دهید.