- Workflow: یکی از مفاهیمی که باید با آن آشنا باشیم workflowها هستند. یک workflow مجموعهایی از jobهایی هستند که در رخدادهای خاصی اجرا میشوند. در واقع یک workflow یک CI pipeline است که با کمک YAML آنها را تعریف میکنیم.
- Runner: اینها به اصطلاح compute machineهایی هستند که workflowها را اجرا میکنند. این runnerها هم میتوانند به صورت سفارشی باشند و هم سرویسهای ارائه شده توسط GitHub باشند.
- Job: مجموعهایی از مراحلی که درون یک runner workspace اجرا میشوند.
- Step: در نهایت stepها هستند که کوچکترین بخش GitHub Actions هستند. stepها میتوانند فایل اسکریپت، Dockerfile یا یک community action باشند.
name: Build Application Code on: [push] jobs: build: runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v2 - name: Install Libraries uses: pip install -r requirements.txt -t . test: runs-on: ubuntu-latest needs: build steps: ...
├── .github │ ├── scripts │ └── workflows ├── README.md ├── assets └── deps
name: Update Recent Blog Posts on: schedule: - cron: "0 0 * * 0" # Run once a week at 00:00 (midnight) on Sunday workflow_dispatch: jobs: update_posts: runs-on: ubuntu-latest steps: - name: Check out repository code uses: actions/checkout@v3 - name: Run the script for fetching latest blog posts shell: pwsh run: | . ./.github/scripts/Get-Posts.ps1 - name: Commit and Push the changes uses: mikeal/publish-to-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Function Get-Posts { Param ( [Parameter(Mandatory = $false)] [string]$rssUrl ) $posts = @() $feed = [xml](Invoke-WebRequest -Uri $rssUrl).Content $feed.rss.channel.item | Select-Object -First 3 | ForEach-Object { $post = [PSCustomObject]@{ Title = $_.title."#cdata-section" ?? $_.title Link = $_.link Description = $_.description."#cdata-section" ?? $_.description PubDate = $_.pubDate } $posts += $post } $posts } Function Get-DntipsPosts { $assemblyPath = "$(Get-Location)/deps/CodeHollow.FeedReader.dll" [Reflection.Assembly]::LoadFile($assemblyPath) $feed = [CodeHollow.FeedReader.FeedReader]::ReadAsync("https://www.dntips.ir/feed/author/%d8%b3%db%8c%d8%b1%d9%88%d8%a7%d9%86%20%d8%b9%d9%81%db%8c%d9%81%db%8c").Result $posts = @() $feed.Items | Select-Object -First 3 | ForEach-Object { $post = [PSCustomObject]@{ Title = $_.Title Link = $_.Link Description = $_.Description PubDate = $_.PublishingDate } $posts += $post } $posts } Function Set-Posts { [CmdletBinding()] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [PSCustomObject[]]$posts, [Parameter(Mandatory = $false)] [string]$marker = "## Recent Blog Posts - English" ) Begin { $readMePath = "./README.md" $readmeContents = Get-Content -Path $readMePath -Raw $markdownTable = "| Link | Published At |`n" $markdownTable += "| --- | --- |`n" } Process { if ($null -eq $_.Title) { return } $date = Get-Date -Date $_.PubDate $link = "[$($_.Title)]($($_.Link))" $markdownTable += "| $($link) | $($date.ToString("dd/MM/yy")) |`n" } End { $updatedContent = $readmeContents -replace "$marker\n([\s\S]*?)(?=#| $)", "$marker`n$($markdownTable)`n" $updatedContent | Set-Content -Path $readMePath } } Function Set-Blogs { $recentBlogPostsStr = "## Recent blog posts -" Get-Posts("https://dev.to/feed/sirwanafifi") | Set-Posts -marker "$recentBlogPostsStr dev.to" Get-Posts("https://sirwan.infohttps://www.dntips.ir/rss.xml") | Set-Posts -marker "$recentBlogPostsStr sirwan.info" Get-DntipsPosts | Set-Posts -marker "$recentBlogPostsStr dntips.ir" } Set-Blogs
name: Update Step Component on: schedule: - cron: "0 18 * * *" workflow_dispatch: jobs: update_steps: runs-on: ubuntu-latest steps: - name: Check out repository code uses: actions/checkout@v3 - name: Run the script for fetching my latest steps shell: pwsh env: STEPS_URI: ${{ secrets.STEPS_URI }} run: | . ./.github/scripts/Get-Steps.ps1 - name: Commit and Push the changes uses: mikeal/publish-to-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Function Set-Steps { Param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [PSObject]$json ) Write-Host ($json | ConvertTo-Json) $SvgPath = "$(Get-Location)/assets/step.svg" $SvgContent = Get-Content -Path $SvgPath -Raw $TextTags = @" <tspan id="step-count" font-weight="bold">$([System.String]::Format("{0:n0}", [int]$json.steps))</tspan> "@ $DatetimeTags = "<text id=""datetime"" x=""800"" y=""72"" font-size=""39"" fill="#99989E"">$($json.date)</text>" $SvgContent = $SvgContent -Replace '<tspan id="step-count" font-weight="bold">.*?</tspan>', $TextTags $SvgContent = $SvgContent -Replace '<text id="datetime" x="800" y="72" font-size="39" fill="#99989E">.*?</text>', $DatetimeTags $SvgContent | Set-Content -Path $SvgPath } Function Get-LatestSteps { Try { $Uri = $env:STEPS_URI Write-Host "Uri: $Uri" $JsonResult = (Invoke-WebRequest -Uri $Uri).Content | ConvertFrom-Json Write-Host "Steps: $($JsonResult.steps)" Return $JsonResult } Catch { Return @{ steps = 0 date = Get-Date -Format "yyyy-MM-dd" } } } Write-Host "Getting latest steps..." Get-LatestSteps | Set-Steps Write-Host "Done!"
public class Person { public Person() { personId= this.GetType().Name + (new Random()).Next(1, int.MaxValue); } }
var student1=new Student(){Name="Iraj",Age=21}; var student1=new Student(){Name="Nima",Age=20}; var student1=new Student(){Name="Sara",Age=25}; var student1=new Student(){Name="Mina",Age=22}; var student1=new Student(){Name="Narges",Age=26}; var teacher1=new Student(){Name="Navaei",Age=45}; var teacher2=new Student(){Name="Imani",Age=50};
public class Person { public Person() { personId= this.GetType().Name + (new Random()).Next(1, int.MaxValue); Debug.Print(personId) } }
using System; using System.Threading; public static class RandomProvider { private static int seed = Environment.TickCount; private static ThreadLocal<Random> randomWrapper = new ThreadLocal<Random>(() => new Random(Interlocked.Increment(ref seed)) ); public static Random GetThreadRandom() { return randomWrapper.Value; } }
تمرین 1 - ایجاد فرم ثبت نام
میخواهیم به برنامه، فرم ثبت نام را که حاوی سه فیلد نام کاربری، کلمهی عبور و نام است، اضافه کنیم. نام کاربری باید از نوع ایمیل باشد. بنابراین اعتبارسنجی مرتبطی نیز باید برای این فیلد تعریف شود. کلمهی عبور وارد شده باید حداقل 5 حرف باشد. همچنین تا زمانیکه اعتبارسنجی فرم تکمیل نشدهاست، باید دکمهی submit فرم، غیرفعال باقی بماند. لینک ورود به این فرم نیز باید به منوی راهبری سایت اضافه شود.
برای حل این تمرین، فایل جدید registerForm.jsx را در پوشهی components ایجاد میکنیم و سپس توسط میانبرهای imrc و cc در VSCode، ساختار ابتدایی کامپوننت RegisterForm را ایجاد کرده و سپس آنرا به صورت زیر تکمیل میکنیم:
- ابتدا در فایل app.js، پس از import ماژول آن:
import RegisterForm from "./components/registerForm";
<Route path="/register" component={RegisterForm} />
<NavLink className="nav-item nav-link" to="/register"> Register </NavLink>
import Joi from "@hapi/joi"; import React from "react"; import Form from "./common/form"; class RegisterForm extends Form { state = { data: { username: "", password: "", name: "" }, errors: {} }; schema = { username: Joi.string() .required() .email({ minDomainSegments: 2, tlds: { allow: ["com", "net"] } }) .label("Username"), password: Joi.string() .required() .min(5) .label("Password"), name: Joi.string() .required() .label("Name") }; doSubmit = () => { // Call the server console.log("Submitted"); }; render() { return ( <div> <h1>Register</h1> <form onSubmit={this.handleSubmit}> {this.renderInput("username", "Username")} {this.renderInput("password", "Password", "password")} {this.renderInput("name", "Name")} {this.renderButton("Register")} </form> </div> ); } } export default RegisterForm;
- سپس state این کامپوننت را با شیءای حاوی دو خاصیت data و error، مقدار دهی اولیه میکنیم. خواص متناظر با المانهای فرم را نیز به صورت یک شیء، به خاصیت data انتساب دادهایم.
- پس از آن، خاصیت schema تعریف شدهاست؛ تا قواعد اعتبارسنجی تک تک فیلدهای فرم را به کمک کتابخانهی Joi، مطابق نیازمندیهایی که در ابتدای تعریف این تمرین مشخص کردیم، ایجاد کند.
- در ادامه، متد doSubmit را ملاحظه میکنید. این متد پس از کلیک بر روی دکمهی Register و پس از اعتبارسنجی موفقیت آمیز فرم، به صورت خودکار فراخوانی میشود.
- در آخر، تعریف فرم ثبتنام را مشاهده میکنید که نکات آنرا در قسمت قبل، با معرفی کامپوننت Form و افزودن متدهای کمکی رندر input و button به آن، بررسی کردیم و در کل با نکات بررسی شدهی در فرم لاگینی که تا به اینجا ایجاد کردیم، تفاوتی ندارد.
تمرین 2- ایجاد فرم ثبت و یا ویرایش یک فیلم
فرم جدید ثبت و ویرایش یک فیلم، نکات بیشتری را به همراه دارد. در اینجا میخواهیم در بالای لیست نمایش فیلمها، یک دکمهی new movie را اضافه کنیم تا با کلیک بر روی آن، به فرم ثبت و ویرایش فیلمها هدایت شویم. این فرم، از فیلدهای یک عنوان متنی، انتخاب ژانر از یک drop down list، تعداد موجود (بین 1 و 100) و امتیاز (بین صفر تا 10) تشکیل شدهاست. همچنین تا زمانیکه اعتبارسنجی فرم تکمیل نشدهاست، دکمهی submit فرم باید غیرفعال باقی بماند. پس از ذخیره شدن این فیلم (در لیست درون حافظهای برنامه)، با مراجعهی به لیست فیلمها و انتخاب آن از لیست (با کلیک بر روی لینک آن)، باید مجددا به همین فرم، در حالت ویرایش این رکورد هدایت شویم. به علاوه اگر در بالای صفحه یک id اشتباه وارد شد، باید صفحهی «پیدا نشد» نمایش داده شود.
کامپوننت MovieForm و مسیریابی آنرا در قسمت 17، تعریف و اضافه کردیم. برای تعریف لینکی به آن، به کامپوننت movies مراجعه کرده و بالای متنی که تعداد کل آیتمهای موجود در بانک اطلاعاتی را نمایش میدهد، المان زیر را اضافه میکنیم:
import { Link } from "react-router-dom"; // ... <div className="col"> <Link to="/movies/new" className="btn btn-primary" style={{ marginBottom: 20 }} > New Movie </Link> <p>Showing {totalCount} movies in the database.</p>
سپس به کامپوننت src\components\movieForm.jsx که پیشتر آنرا اضافه کرده بودیم، مراجعه کرده و به صورت زیر آنرا تکمیل میکنیم:
import Joi from "@hapi/joi"; import React from "react"; import { getGenres } from "../services/fakeGenreService"; import { getMovie, saveMovie } from "../services/fakeMovieService"; import Form from "./common/form"; class MovieForm extends Form { state = { data: { title: "", genreId: "", numberInStock: "", dailyRentalRate: "" }, genres: [], errors: {} };
- سپس این کامپوننت نیز از کامپوننت Form ارث بری میکند تا به امکانات ویژهی آن دسترسی پیدا کند.
- در ادامه در خاصیت state، طبق روالی که در کامپوننت فرم درنظر گرفتهایم، دو خاصیت data و errors باید حضور داشته باشند. در خاصیت data، شیءای که نام خاصیتهای آن با فیلدهای فرم تطابق دارد، ذکر شدهاند. در اینجا برای ذخیره سازی اطلاعات انتخاب شدهی از drop down list مرتبط با ژانرهای سینمایی، از خاصیت genreId استفاده میشود؛ این تنها اطلاعاتی است که از کل آیتمهای یک drop down list نیاز داریم. آرایهی genres که آیتمهای این drop down list را مقدار دهی میکند، در روال componentDidMount، از سرویس مرتبطی دریافت و مقدار دهی خواهد شد.
در ادامهی کدهای کامپوننت MovieForm، کدهای schema اعتبارسنجی شیء data را ملاحظه میکنید:
schema = { _id: Joi.string(), title: Joi.string() .required() .label("Title"), genreId: Joi.string() .required() .label("Genre"), numberInStock: Joi.number() .required() .min(0) .max(100) .label("Number in Stock"), dailyRentalRate: Joi.number() .required() .min(0) .max(10) .label("Daily Rental Rate") };
اکنون به مرحلهی componentDidMount میرسیم:
componentDidMount() { const genres = getGenres(); this.setState({ genres }); const movieId = this.props.match.params.id; if (movieId === "new") return; const movie = getMovie(movieId); if (!movie) return this.props.history.replace("/not-found"); this.setState({ data: this.mapToViewModel(movie) }); }
- پس از آن، نحوهی دریافت پارامتر id مسیریابی رسیده را ملاحظه میکنید. این id اگر به "new" تنظیم شده بود، یعنی قرار است، اطلاعات جدیدی ثبت شوند. بنابراین متد جاری را خاتمه میدهیم (چون کار ادامهی این متد، مقدار دهی اولیهی تمام فیلدهای فرم، بر اساس اطلاعات شیء دریافت شدهی از سرویس فیلمها است). در غیراینصورت (و با مشخص بودن id)، با استفاده از این id و متد getMovie سرویس src\services\fakeMovieService.js، سعی خواهیم کرد تا اطلاعات شیء movie متناظری را دریافت کنیم. اگر خروجی این متد null بود، یعنی id وارد شده معتبر نیست. به همین جهت کاربر را به صفحهی not-found هدایت میکنیم. اگر دقت کنید در اینجا بجای متد push، از متد replace استفاده کردهایم. چون اگر از متد push استفاده میکردیم و کاربر بر روی دکمهی back مرورگر کلیک میکرد، دوباره به همین صفحه، با id غیرمعتبر قبلی وارد میشد و یک حلقهی بیپایان رخ میداد. همچنین به return ای هم که به همراه متد replace استفاده شده، دقت کنید. کار redirect به یک صفحهی دیگر، به معنای عدم اجرای کدهای پس از آن نیست. بنابراین اگر میخواهیم کار این متد با redirect، به پایان برسد، ذکر return الزامی است.
- در پایان این متد، خاصیت data موجود در state را به روز رسانی میکنیم؛ تا سبب رندر فرم، با اطلاعات شیء movie یافت شده گردد و چون ساختار شیء movie دریافت شدهی از سرویس، با ساختار data تعریف شدهی در state یکی نیست، نیاز به نگاشت این دو به هم، توسط متد سفارشی mapToViewModel زیر است:
mapToViewModel(movie) { return { _id: movie._id, title: movie.title, genreId: movie.genre._id, numberInStock: movie.numberInStock, dailyRentalRate: movie.dailyRentalRate }; }
در ادامهی کدهای کامپوننت فرم فیلمها، به متد doSubmit میرسیم:
doSubmit = () => { saveMovie(this.state.data); this.props.history.push("/movies"); };
در انتهای این کامپوننت نیز به متد رندر آن میرسیم:
render() { return ( <div> <h1>Movie Form</h1> <form onSubmit={this.handleSubmit}> {this.renderInput("title", "Title")} {this.renderSelect("genreId", "Genre", this.state.genres)} {this.renderInput("numberInStock", "Number in Stock", "number")} {this.renderInput("dailyRentalRate", "Rate")} {this.renderButton("Save")} </form> </div> ); }
برای تعریف متد جدید renderSelect به این صورت عمل میکنیم:
- ابتدا فایل جدید src\components\common\select.jsx را ایجاد کرده و سپس آنرا جهت نمایش یک drop down list، ویرایش میکنیم:
import React from "react"; const Select = ({ name, label, options, error, ...rest }) => { return ( <div className="form-group"> <label htmlFor={name}>{label}</label> <select name={name} id={name} {...rest} className="form-control"> <option value="" /> {options.map(option => ( <option key={option._id} value={option._id}> {option.name} </option> ))} </select> {error && <div className="alert alert-danger">{error}</div>} </div> ); }; export default Select;
- پس از آن به کامپوننت src\components\common\form.jsx مراجعه کرده و متد رندر آنرا اضافه میکنیم:
import Select from "./select"; // ... class Form extends Component { // ... renderSelect(name, label, options) { const { data, errors } = this.state; return ( <Select name={name} value={data[name]} label={label} options={options} onChange={this.handleChange} error={errors[name]} /> ); } }
تمرین 3- جستجوی در لیست فیلمها
میخواهیم در بالای لیست نمایش فیلمها، یک search box را قرار دهیم تا توسط آن بتوان بر اساس عنوان وارد شده، در فیلمهای موجود جستجو کرد. همچنین این جستجو قرار است کلی بوده و حتی در صورت انتخاب ژانر خاصی از منوی کنار صفحه، باید در کل اطلاعات موجود جستجو کند. به علاوه اگر کاربر ژانری را انتخاب کرد، این text box باید خالی شود.
برای اینکار ابتدا فایل جدید src\components\searchBox.jsx را ایجاد کرده و به صورت زیر آنرا تکمیل میکنیم:
import React from "react"; const SearchBox = ({ value, onChange }) => { return ( <input type="text" name="query" className="form-control my-3" placeholder="Search..." value={value} onChange={e => onChange(e.currentTarget.value)} /> ); }; export default SearchBox;
سپس به کامپوننت movies مراجعه کرده و آنرا ذیل متن نمایش تعداد رکوردها، درج میکنیم:
<p>Showing {totalCount} movies in the database.</p> <SearchBox value={searchQuery} onChange={this.handleSearch} />
import SearchBox from "./searchBox"; //... class Movies extends Component { state = { //... selectedGenre: {}, searchQuery: "" }; handleSearch = query => { this.setState({ searchQuery: query, selectedGenre: null, currentPage: 1 }); }; handleGenreSelect = genre => { console.log("handleGenreSelect", genre); this.setState({ selectedGenre: genre, searchQuery: "", currentPage: 1 }); };
متد handleGenreSelect را نیز اندکی تغییر دادهایم تا اگر گروهی انتخاب شد، مقدار searchQuery را خالی کند. اگر در اینجا searchQuery را به نال تنظیم میکردیم، controlled component جعبهی جستجو، تبدیل به کامپوننت کنترل نشدهای میشد و در این حالت، React، اخطار تبدیل بین این دو را صادر میکرد.
در آخر، ابتدای متد getPageData هم جهت اعمال searchQuery، به صورت زیر تغییر میکند:
getPagedData() { const { pageSize, currentPage, selectedGenre, movies: allMovies, sortColumn, searchQuery } = this.state; let filteredMovies = allMovies; if (searchQuery) { filteredMovies = allMovies.filter(m => m.title.toLowerCase().startsWith(searchQuery.toLowerCase()) ); } else if (selectedGenre && selectedGenre._id) { filteredMovies = allMovies.filter(m => m.genre._id === selectedGenre._id); }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-21.zip
یک راه حل دیگر استفاده از توابع نوع Scalar میباشد؛ به این صورت که میتوان مقدار استخراج شده از جدول را به تابع تعریف شده فرستاد و در ازاء، مقدار بازگشتی مناسبی را در خروجی مشاهده کرد. حال به یک مثال توجه کنید:
Select Case Gen when 0 then 'مرد' when 1 then 'زن' end As Gen From Table
CREATE FUNCTION fcGenName ( @Gen tinyint ) RETURNS nvarchar(20) AS BEGIN -- Declare the return variable here DECLARE @gen nvarchar(20) -- Add the T-SQL statements to compute the return value here set @gen = (SELECT case @Gen when 0 then 'مرد' when 1 then 'زن' end as d) -- Return the result of the function RETURN @gen END
Select fcGenName(Gen) From Table
تهیه خروجی RSS در برنامههای ASP.NET MVC
فید سایت امروز از کار افتاده بود. علت آن وجود یک سری کاراکتر غیرمجاز XML در متن بود که باید به نحو ذیل پاک شوند:
private static readonly Regex _matchHexadecimalSymbols = new Regex("[\x00-\x08\x0B\x0C\x0E-\x1F]", RegexOptions.IgnoreCase | RegexOptions.Compiled); /// <summary> /// there are a lot of symbols which can't be in xml code. /// </summary> public static string RemoveHexadecimalSymbols(this string txt) { return string.IsNullOrWhiteSpace(txt) ? string.Empty : _matchHexadecimalSymbols.Replace(txt, string.Empty); }
قبل از اینکه این موضوع را بررسی کنیم باید با دستور Truncate و Delete آشنا شویم.
DELETE FROM table_name WHERE some_column=some_value
DELETE FROM Customers WHERE Country=’USA’ GO
اما نکته مهمی که دستور Delete دارد این است کار این دستور به شکل Transactional میباشد. یعنی یا کلیه رکوردهایی که Country آنها USA است حذف میشود و یا هیچکدام از آنها. پس اگر شما 200000 رکورد داشته باشید که در این شرط صدق کند اگر وسط کار Delete (البته اگر عملیات حذف طولانی باشد) منصرف شوید میتوانید با Cancel کردن این دستور عملیات Rollback Transaction را به خودکار توسط SQL Server داشته باشید. در صورتیکه عملیات Cancel را انجام دهید SQL Server از Log File برای بازگرداندن مقادیر حذف شده استفاده خواهد کرد.
شکل کلی استفاده از این دستور به صورت زیر میباشد.
TRUNCATE TABLE table_name GO
TRUNCATE TABLE Customers GO
2- دستور Truncate Table در Log File آدرس Page و مقدار فضای آزاد شده (کمترین میزان Log) را مینویسد اما در صورتیکه دستور Delete در Log هر رکوردی را که
قرار است حذف شود را در Log File ثبت مینماید.
3- دستور Truncate Table باعث میشود که Pageهای متعلق به جدول deallocate شوند. deallocate شدن Pageها این معنی را میدهد که رکوردهای موجود در جدول واقعاً حذف نشوند بلکه Extentهای مربوط به آن Pageها علامت Empty خورده تا دفعات بعد مورد استفاده قرار گیرند اما دستور Delete به طور فیزیکی محتوای Pageها مربوط به جدول را خالی میکند.
نکته : پس از Truncate شدن رکوردها امکان بازگشت آنها وجود ندارد.
4- در صورتیکه جدول شما دارای ایندکس باشد. دستور Truncate Table آزاد کردن فضای مربوط به ایندکس را در یک مرحله انجام میدهد(مطابق بند 3) همچنین Log مربوط به این حذف به شکل حداقل (مطابق بند 2) در Log File ثبت میشود. اما دستور Delete هر رکوردی را که از ایندکس حذف میکند در Log File ثبت میکند.
5- Trigger مربوط به دستور Delete به هیچ عنوان هنگام اجرای دستور Truncate Table فعال نمیشود. در صورتیکه با اجرای دستور Delete تریگر آن فعال خواهد شد.
6- در صورتیکه جدول شما دارایRelation) Reference) باشد امکان استفاده از دستور Truncate Table وجود ندارد. لازم به ذکر است حتی اگر Reference را غیر فعال کنید
باز هم امکان استفاده از دستور Truncate Table وجود نخواهد داشت و تلاش برای اجرای دستور Truncate Table باعث نمایش خطای زیر خواهد شد.
در صورتیکه در دستور Delete امکان حذف رکوردها به ازای جداولی که دارای Relation هستند وجود دارد. فقط باید به این نکته توجه کنید که ترتیب حذف رکوردها از جداول Master و Detail را رعایت کنید.
7- دستور Truncate Table مقدار Identity را Reset کرده و آن را به Seed (هسته/مقدار اولیه) بر میگرداند. در صورتیکه دستور Delete تاثیری بر روی مقدار Identity ندارد
8- دستور Truncate Table تنها توسط کاربرانی قابل اجرا است که نقش DB_Owner و یا SysAdmin را داشته باشند در صورتیکه دستور Delete توسط هر کاربری که مجوز Delete بر روی جدول را داشته باشد قابل اجرا میباشد.
9- پس از اجرای دستور Truncate Table تعداد رکوردهای حذف شده نمایش داده نمیشود. در صورتیکه هنگام اجرای دستور Delete تعداد رکوردهای حذف شده نمایش داده میشود.
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddNodeServices(); }
حال میتوانید به وهلهای از اینترفیس INodeServices، از طریق تزریق وابستگیها دسترسی داشته باشید. اینترفیس INodeServices یک Api میباشد و مشخص میکند که کدام قطعه از کد NET. میتواند کد جاوااسکریپتی را که در محیط Node اجرا میشود، فراخوانی کند. همچنین میتوانید از خاصیت FromServices برای دریافت وهلهای از اینترفیس INodeServices در اکشن خود استفاده نمایید.
public async Task<IActionResult> Add([FromServices] INodeServices nodeServices) { var num1 = 10; var num2 = 20; var result = await nodeServices.InvokeAsync<int>("AddModule.js", num1, num2); ViewData["ResultFromNode"] = $"Result of {num1} + {num2} is {result}"; return View(); }
سپس کد جاوااسکریپتی متناظر با تابعی را که توسط متد InvokeAsync فراخوانی میشود، به صورت زیر مینویسیم:
module.exports = function(callback, num1, num2) { var result = num1 + num2; callback(null, result); };
حال بیاییم مثالی دیگر را مرور کنیم. میخواهیم از صفحه وب درخواستی، عکسی را تهیه کنیم. بدین منظور از کتابخانه url-to-image استفاده میکنیم. برای نصب آن دستور npm install --save url-to-image را در خط فرمان تایپ میکنیم.
بعد از اتمام نصب این بسته، متدی را برای دریافت اطلاعات ارسالی این کتابخانه تدارک میبینیم.
[HttpPost] public async Task<IActionResult> GenerateUrlPreview([FromServices] INodeServices nodeServices) { var url = Request.Form["Url"].ToString(); var fileName = System.IO.Path.ChangeExtension(DateTime.UtcNow.Ticks.ToString(), "jpeg"); var file = await nodeServices.InvokeAsync<string>("UrlPreviewModule.js", url, System.IO.Path.Combine("PreviewImages", fileName)); return Content($"/Home/Download?img={fileName}"); } public IActionResult Download() { var image = Request.Query["img"].ToString(); var fileName = System.IO.Path.Combine("PreviewImages", image); var isExists = System.IO.File.Exists(fileName); if (isExists) { Response.Headers.Add($"Content-Disposition", "attachment; filename=\"" + image + "\""); var bytes = System.IO.File.ReadAllBytes(fileName); return File(bytes, "image/jpeg"); } else { return NotFound(); } }
سپس متد UrlPreviewModule.js را به صورت زیر مینویسیم:
var urlToImage = require('url-to-image'); module.exports = function (callback, url, imageName) { urlToImage(url, imageName).then(function () { callback(null, imageName); }).catch(function (err) { callback(err, imageName); }); };
سرویسهای Node به توسعه دهندگان ASP.NET Core امکان استفاده از اکوسیستم NPM را که دارای قابلیتهای فراوانی میباشد، میدهد.
// <Name>Aggregate Type Complexity</Name> from t in Application.Types let aggregateTypeCC = t.MethodsAndContructors.Sum(m => m.CyclomaticComplexity / 10) let rawCC = t.MethodsAndContructors.Sum(m => m.CyclomaticComplexity) // optional optimization if not comparing rawCC // where aggregateCC >= 10 orderby aggregateTypeCC descending select new { t, aggregateTypeCC, rawCC }
var searchLocation = DbGeography.FromText(String.Format("POINT({0} {1})", longitude, latitude)); var nearbyLocations = (from location in _context.GeoLocations where // (Additional filtering criteria here...) select new { LocationID = location.ID, // ... Distance = searchLocation.Distance( DbGeography.FromText("POINT(" + location.Longitude + " " + location.Latitude + ")")) }) .OrderBy(location => location.Distance) .ToList();