برای استفاده از jQuery Ajax یکی از روشهای ارسال دیتا به برنامه، تبدیل دادهها به فرمت JSON میباشد. برای دادههای ساده، تشخیص این فرمت ساده است. مثلا اگر امضای تابع وب سرویس اجکس ما به صورت زیر باشد:
public static bool IsUserAvailable(string username)
{'username':'value'}
برای این منظور در دات نت 3 و نیم، کلاسی جهت انجام اینگونه تبدیلات پیش بینی شده است که شرح مختصر آن به صورت زیر است:
ابتدا باید ارجاعی را به اسمبلی system.web.extensions به برنامه افزود و سپس جهت سهولت کار میتوان یک extension method از کلاس JavaScriptSerializer مهیا در فضای نام System.Web.Script.Serialization ایجاد کرد:
public static string ToJson(this object data)
{
return new JavaScriptSerializer().Serialize(data);
}
public static string GetJsonTest0()
{
var data = "a1";
return data.ToJson();
}
public static string GetJsonTest1()
{
var data = new List<string> { "a1", "a2", "a3" };
return data.ToJson();
}
public static string GetJsonTest2()
{
var lst =
new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>("Name", "علی"),
new KeyValuePair<string, object>("Number", 10),
new KeyValuePair<string, object>("Desc", "منابع مورد نیاز")
};
return lst.ToJson();
}
"a1"
["a1","a2","a3"]
[{"Key":"Name","Value":"علی"},{"Key":"Number","Value":10},{"Key":"Desc","Value":"منابع مورد نیاز"}]
این کلاس همچنین قابلیت Deserialize و تبدیل دادههایی به فرمت JSON به اشیاء مورد نظر ما را نیز دارا است.
«بررسی مفاهیم معکوس سازی وابستگیها و ابزارهای مرتبط با آن»
«اصول طراحی SOLID»
«مطالعهی بیشتر»
تزریق وابستگیها (یا Dependency injection = DI) به معنای ارسال نمونهای/وهلهای از وابستگی (یک سرویس) به شیء وابستهی به آن (یک کلاینت) است. در فرآیند تزریق وابستگیها، یک کلاس، وهلههای کلاسهای دیگر مورد نیاز خودش را بجای وهله سازی مستقیم، از یک تزریق کننده دریافت میکند. بنابراین بجای نوشتن newها در کلاس جاری، آنها را به صورت وابستگیهایی در سازندهی کلاس تعریف میکنیم تا توسط یک IoC Container تامین شوند. در اینجا به فریم ورکهایی که کار وهله سازی این وابستگیها را انجام میدهند، IoC Container و یا DI container میگوییم (IoC = inversion of control ).
چندین نوع تزریق وابستگیها وجود دارند که دو حالت زیر، عمومیترین آنها است:
الف) تزریق در سازندهی کلاس: لیست وابستگیهای یک کلاس، به عنوان پارامترهای سازندهی آن ذکر میشوند.
ب) تزریق در خواص یا Setter injection: کلاینت خواصی get و set را به صورت public معرفی میکند و سپس IoC Container با وهله سازی آنها، وابستگیهای مورد نیاز را تامین خواهد کرد.
تزریق وابستگیها در ASP.NET Core
برخلاف نگارشهای قبلی ASP.NET، این نگارش جدید از ابتدا با دید پشتیبانی کامل از DI طراحی شدهاست و این مفهوم، در سراسر اجزای آن به صورت یکپارچهای پشتیبانی میشود. همچنین به همراه یک minimalistic DI container توکار نیز هست .
این IoC Container توکار از 4 حالت طول عمر ذیل پشتیبانی میکند:
- instance: در هربار نیاز به یک وابستگی خاص، تنها یک وهله از آن در اختیار مصرف کننده قرار میگیرد و در اینجا شما هستید که مسئول تعریف نحوهی وهله سازی این شیء خواهید بود (برای بار اول).
- transient: هربار که نیاز به وابستگی خاصی بود، یک وهلهی جدید از آن توسط IoC Container تولید و ارائه میشود.
- singleton: در این حالت تنها یک وهله از وابستگی درخواست شده در طول عمر برنامه تامین میشود.
- scoped: در طول عمر یک scope خاص، تنها یک وهله از وابستگی درخواست شده، در اختیار مصرف کنندهها قرار میگیرد. برای مثال مرسوم است که به ازای یک درخواست وب، تنها یک وهله از شیءایی خاص در اختیار تمام مصرف کنندههای آن قرار گیرد (single instance per web request).
طول عمر singleton، برای سرویسها و کلاسهای config مناسب هستند. به این ترتیب به کارآیی بالاتری خواهیم رسید و دیگر نیازی نخواهد بود تا هر بار این اطلاعات خوانده شوند. حالت scoped برای وهله سازی الگوی واحد کار و پیاده سازی تراکنشها مناسب است. برای مثال در طی یک درخواست وب، یک تراکنش باید صورت گیرد.
حالت scoped در حقیقت نوع خاصی از حالت transient است. در حالت transient صرفنظر از هر حالتی، هربار که وابستگی ویژهای درخواست شود، یک وهلهی جدید از آن تولید خواهد شد. اما در حالت scoped فقط یک وهلهی از وابستگی مورد نظر، در بین تمام اشیاء وابستهی به آن، در طول عمر آن scope تولید میشود.
بنابراین در برنامههای وب دو نوع singleton برای معرفی کلاسهای config و نوع scoped برای پیاده سازی تراکنشها و همچنین بالابردن کارآیی برنامه در طی یک درخواست وب (با عدم وهله سازی بیش از اندازهی از کلاسهای مختلف مورد نیاز)، بیشتر از همه به کار برده میشوند.
یک مثال کاربردی: بررسی نحوهی تزریق یک سرویس سفارشی به کمک IoC Container توکار ASP.NET Core
مثال جاری که بر اساس ASP.NET Core Web Application و با قالب خالی آن ایجاد شدهاست، دارای نام فرضی Core1RtmEmptyTest است. در همین پروژه بر روی پوشهی src، کلیک راست کرده و گزینهی Add new project را انتخاب کنید و سپس یک پروژهی جدید از نوع NET Core -> Class library. را به آن، با نام Core1RtmEmptyTest.Services اضافه کنید (تصویر فوق).
در ادامه کلاس نمونهی سرویس پیامها را به همراه اینترفیس آن، با محتوای زیر به آن اضافه کنید:
namespace Core1RtmEmptyTest.Services { public interface IMessagesService { string GetSiteName(); } public class MessagesService : IMessagesService { public string GetSiteName() { return "DNT"; } } }
انجام اینکار معادل است با افزودن یک سطر ذیل به فایل project.json پروژه:
{ "dependencies": { // same as before "Core1RtmEmptyTest.Services": "1.0.0-*" },
public void Configure( IApplicationBuilder app, IHostingEnvironment env, IMessagesService messagesService)
public void Configure( IApplicationBuilder app, IHostingEnvironment env, IMessagesService messagesService) { app.Run(async context => { var siteName = messagesService.GetSiteName(); await context.Response.WriteAsync($"Hello {siteName}"); }); }
System.InvalidOperationException No service for type 'Core1RtmEmptyTest.Services.IMessagesService' has been registered. at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType) at Microsoft.AspNetCore.Hosting.Internal.ConfigureBuilder.Invoke(object instance, IApplicationBuilder builder) System.Exception Could not resolve a service of type 'Core1RtmEmptyTest.Services.IMessagesService' for the parameter 'messagesService' of method 'Configure' on type 'Core1RtmEmptyTest.Startup'. at Microsoft.AspNetCore.Hosting.Internal.ConfigureBuilder.Invoke(object instance, IApplicationBuilder builder) at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<IMessagesService, MessagesService>(); }
پس از اینکار، مجددا برنامه را اجرا کنید. اکنون این خروجی باید مشاهده شود:
و به این معنا است که اکنون IoC Cotanier توکار ASP.NET Core، میداند زمانیکه به IMessagesService رسید، چگونه باید آنرا وهله سازی کند.
چه سرویسهایی به صورت پیش فرض در IoC Container توکار ASP.NET Core ثبت شدهاند؟
در ابتدای متد ConfigureServices یک break point را قرار داده و برنامه را در حالت دیباگ اجرا کنید:
همانطور که ملاحظه میکنید، به صورت پیش فرض 16 سرویس در اینجا ثبت شدهاند که تاکنون با دو مورد از آنها کار کردهایم.
امکان تزریق وابستگیها در همه جا!
در مثال فوق، سرویس سفارشی خود را در متد Configure کلاس آغازین برنامه تزریق کردیم. نکتهی مهم اینجا است که برخلاف نگارشهای قبلی ASP.NET MVC (یعنی بدون نیاز به تنظیمات خاصی برای قسمتهای مختلف برنامه)، میتوان این تزریقها را در کنترلرها، در میان افزارها، در فیلترها در ... همه جا و تمام اجزای ASP.NET Core 1.0 انجام داد و دیگر اینبار نیازی نیست تا نکتهی ویژهی نحوهی تزریق وابستگیها در فیلترها یا کنترلرهای ASP.NET MVC را یافته و سپس اعمال کنید. تمام اینها از روز اول کار میکنند. همینقدر که کار ثبت سرویس خود را در متد ConfigureServices انجام دادید، این سرویس در سراسر اکوسیستم ASP.NET Core، قابل دسترسی است.
نیاز به تعویض IoC Container توکار ASP.NET Core
قابلیت تزریق وابستگیهای توکار ASP.NET Core صرفا جهت برآورده کردن نیازمندیهای اصلی آن طراحی شدهاست و نه بیشتر. بنابراین توسط آن قابلیتهای پیشرفتهای را که سایر IoC Containers ارائه میدهند، نخواهید یافت. برای مثال تعویض امکانات تزریق وابستگیهای توکار ASP.NET Core با StructureMap این مزایا را به همراه خواهد داشت:
• امکان ایجاد child/nested containers (پشتیبانی از سناریوهای چند مستاجری)
• پشتیبانی از Setter Injection
• امکان انتخاب سازندهای خاص (اگر چندین سازنده تعریف شده باشند)
• سیم کشی خودکار یا Conventional "Auto" Registration (برای مثال اتصال اینترفیس IName به کلاس Name به صورت خودکار و کاهش تعداد تعاریف ابتدای برنامه)
• پشتیبانی توکار از Lazy و Func
• امکان وهله سازی از نوعهای concrete (یا همان کلاسهای معمولی)
• پشتیبانی از مفاهیمی مانند Interception و AOP
• امکان اسکن اسمبلیهای مختلف جهت یافتن اینترفیسها و اتصال خودکار آنها (طراحیهای افزونه پذیر)
روش تعویض IoC Container توکار ASP.NET Core با StructureMap
جزئیات این جایگزین کردن را در مطلب «جایگزین کردن StructureMap با سیستم توکار تزریق وابستگیها در ASP.NET Core 1.0» میتوانید مطالعه کنید.
یا میتوانید از روش فوق استفاده کنید و یا اکنون قسمتی از پروژهی رسمی استراکچرمپ در آدرس https://github.com/structuremap/structuremap.dnx جهت کار با NET Core. طراحی شدهاست. برای کار با آن نیاز است این مراحل طی شوند:
الف) دریافت بستهی نیوگت StructureMap.Dnx
برای این منظور بر روی گره references کلیک راست کرده و گزینهی manage nuget packages را انتخاب کنید. سپس در برگهی browse آن، StructureMap.Dnx را جستجو کرده و نصب نمائید (تیک مربوط به انتخاب pre releases هم باید انتخاب شده باشد):
انجام این مراحل معادل هستند با افزودن یک سطر ذیل به فایل project.json برنامه:
{ "dependencies": { // same as before "StructureMap.Dnx": "0.5.1-rc2-final" },
پس از نصب بستهی StructureMap.Dnx، به کلاس آغازین برنامه مراجعه کرده و این تغییرات را اعمال کنید:
public class Startup { public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddDirectoryBrowser(); var container = new Container(); container.Configure(config => { config.Scan(_ => { _.AssemblyContainingType<IMessagesService>(); _.WithDefaultConventions(); }); //config.For<IMessagesService>().Use<MessagesService>(); config.Populate(services); }); container.Populate(services); return container.GetInstance<IServiceProvider>(); }
سپس وهلهای از IServiceProvider، توسط استراکچرمپ تامین شده و بازگشت داده میشود تا بجای IoC Container توکار ASP.NET Core استفاده شود.
در این مثال چون در متد Scan، کار بررسی اسمبلی لایه سرویس برنامه با قراردادهای پیش فرض استراکچرمپ انجام شدهاست، دیگر نیازی به سطر تعریف config.For نیست. در اینجا هرگاه IName ایی یافت شد، به کلاس Name متصل میشود (name هر نامی میتواند باشد).
public class CollectionClass : IEnumerable { private string[] mySet = { "Array of String 1", "Array of String 2", "Array of String 3" }; public IEnumerator GetEnumerator() { //return arrayStrings.GetEnumerator(); foreach (var element in mySet ) { yield return element; } } }
CollectionClass c = new CollectionClass(); foreach (var element in c) { Console.WriteLine(element); }
public class CollectionClass : IEnumerable { //private readonly string[] arrayStrings = { "Array of String 1", "Array of String 2", "Array of String 3" }; private List<string> mySet= new List<string>() { "Array of String 1", "Array of String 2", "Array of String 3" }; public IEnumerator GetEnumerator() { foreach (var element in mySet ) { yield return element; } } }
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);
منابع:
در واقع ما یک سیستمی داریم که شامل مدلی است از دیتاهای ما و از این مدل برای کوئری گرفتن از دیتابیس استفاده میشود، که البته برای بیشتر پروژههای نرم افزاری، معماری درست و ترجیح داده شدهای هم میباشد.
زمانیکه نیازهای پروژه روز به روز افزورده و پیچیدهتر میشود، مدل CRUD بصورت پیوسته از ارزشش کاسته میشود و از آن سادگی اولیهی در درک و خوانایی آن دور خواهد شد.
ذات CQRS بر آن است که شما مدلهای مختلفی را برای خواندن و نوشتن دیتا داشته باشید. الگوی آن چیزی شبیه به تصویر زیر است
ES از برنامه نویسان میخواهد که مدل سنتی CRUD را فراموش کرده و بجای آن تغییراتی را که روی دیتا صورت گرفته، نیز درج نمایند. اینکار به وسیلهی یک دیتابیس Append-only انجام میشود که به نام Event Store شناخته میشود.
در این معماری ما همهی تغییرات روی دیتا را به صورت Serialize Event ذخیره میکنیم که میتواند دوباره در هر زمانی اجرا شده و current state هر objectی را در اختیار بگذارد.
این روش به ما کمک بزرگی میکند تا وضعیت یک object را در گذشته به راحتی پیدا کنیم و از آن میتوان به غیر از فوایدی که دارد، به عنوان یک Logger نیز استفاده نمود. به دلیل اینکه جزء به جزء تغییرات بر روی state سیستم، در آن ثبت شده است. از آنجاییکه دیتا بصورت serialize ذخیره میشود، بارگزاری آن نیز با سرعت بالایی انجام خواهد شد.
public class Movie : AggregateRoot { public string Title { get; set; } public DateTime ReleaseDate { get; set; } public int RunningTimeMinutes { get; set; } public Movie() { } public Movie(Guid movieId, string title, DateTime releaseDate, int runningTimeMinutes) { //پیاده سازی خواهد شد } }
public class CreateMovieCommand : ICommand { public string Title { get; set; } public DateTime ReleaseDate { get; set; } public int RunningTimeMinutes { get; set; } public CreateMovieCommand(string title, DateTime releaseDate, int runningTime) { Title = title; ReleaseDate = releaseDate; RunningTimeMinutes = runningTime; } }
public class CreateMovieCommandHandler : CommandHandler<CreateMovieCommand> { protected IDomainRepository _repository; public CreateMovieCommandHandler(IDomainRepository repository) { _repository = repository; } public override void Handle(CreateMovieCommand command) { var movie = new Domain.Movie(Guid.NewGuid(), command.Title, command.ReleaseDate, command.RunningTimeMinutes); _repository.Save(movie); } }
public class MovieCreatedEvent : DomainEvent { public Guid MovieId { get { return AggregateRootId; } set { AggregateRootId = value;} } public string Title { get; set; } public DateTime ReleaseDate { get; set; } public int RunningTimeMinutes { get; set; } public MovieCreatedEvent(Guid movieId, string title, DateTime releaseDate, int runningTime) { MovieId = movieId; Title = title; ReleaseDate = releaseDate; RunningTimeMinutes = runningTime; } }
public class Movie : AggregateRoot { public string Title { get; set; } public DateTime ReleaseDate { get; set; } public int RunningTimeMinutes { get; set; } public Movie(Guid movieId, string title, DateTime releaseDate, int runningTimeMinutes) { Apply(new MovieCreatedEvent(Guid.NewGuid(), title, releaseDate, runningTimeMinutes)); } }
public class MovieEventHandler : IHandleDomainEvents<MovieCreatedEvent> { public void Handle(MovieCreatedEvent createdEvent) { using (MoviesContext entities = new MoviesContext()) { entities.Movies.Add(new Movie() { Id = createdEvent.AggregateRootId, Title = createdEvent.Title, ReleaseDate = createdEvent.ReleaseDate, RunningTimeMinutes = createdEvent.RunningTimeMinutes }); entities.SaveChanges(); } } }
protected void OnMovieCreated(MovieCreatedEvent domainEvent) { Id = domainEvent.AggregateRootId; Title = domainEvent.Title; ReleaseDate = domainEvent.ReleaseDate; RunningTimeMinutes = domainEvent.RunningTimeMinutes; }
- ذاتا پیاده سازی این مدل سخت و دشوار است و از آنجاییکه سادگی در پیاده سازی سیستمهای نرم افزاری، یک اصل مهم محسوب میشود، بنابراین استفاده از این مدل محدود میشود به سیستمهای نرم افزاری که مزیتهای گفته شده در قسمت فوق برایشان حیاتی محسوب شود.
- برای پیاده سازی سیستمی با این مدل احتیاج به تیم توسعهای است که با مفاهیم آن کاملا آشنا باشد.
- هر چند امروزه فضای فیزیکی برای ذخیره سازی دیتا ارزان محسوب میشود، اما به هر حال استفاده از این مدل به همراه ES، حجم زیادی از Disk space را خواهد گرفت.
- همانطور که دیدید برای پیاده سازی یک Insert ساده، حجم زیادی کد نوشته شدهاست. بنابراین تولید اینگونه نرم افزارها به زمان بیشتری نیاز دارد.
public class CompanyModel { public int Id { get; set; } public string CompanyName { get; set; } public string CompanyAbbr { get; set; } public Product Product { set; get; } } public class Product { public int Id { set; get; } }
var data = PropertyExtensions.PropertyName<CompanyModel>(x => x.Product.Id);
راه حلی که از کدهای EF برای این مساله استخراج شده به صورت زیر است (نمونهاش متد Include تو در تو بر روی چند خاصیت):
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; namespace PropertyExtensionsApp { public class PropertyHelper : ExpressionVisitor { private Stack<string> _stack; public string GetNestedPropertyPath(Expression expression) { _stack = new Stack<string>(); Visit(expression); return _stack.Aggregate((s1, s2) => s1 + "." + s2); } protected override Expression VisitMember(MemberExpression expression) { if (_stack != null) _stack.Push(expression.Member.Name); return base.VisitMember(expression); } public string GetNestedPropertyName<TEntity>(Expression<Func<TEntity, object>> expression) { return GetNestedPropertyPath(expression); } } }
var name = new PropertyHelper().GetNestedPropertyName<CompanyModel>(x => x.Product.Id);
نحوه استفاده از ViewModel در ASP.NET MVC
public class ResumeViewModel { public ResumeViewModel() { } public ResumeViewModel(IEnumerable<Resume> resum, IEnumerable<Work_Experience_Job_Seeker> workExperienceJobSeekerOfViewModel, IEnumerable<Job_Expertises> jobExpertisesOfViewModel, IEnumerable<Degrees_Work_Experience_Required> degreesWorkExperienceRequiredOfViewModel, IEnumerable<Specialized_Course> specializedCourseOfViewModel, IEnumerable<Book_Published> bookPublishedOfViewModel, IEnumerable<Basic_Table> basicTable) { ResumeOfViewModel = resum; WorkExperienceJobSeekerOfViewModel = workExperienceJobSeekerOfViewModel; JobExpertisesOfViewModel = jobExpertisesOfViewModel; DegreesWorkExperienceRequiredOfViewModel = degreesWorkExperienceRequiredOfViewModel; SpecializedCourseOfViewModel = specializedCourseOfViewModel; BookPublishedOfViewModel = bookPublishedOfViewModel; BasicTable = basicTable; } public IEnumerable<Resume> ResumeOfViewModel { get; set; } public IEnumerable<Work_Experience_Job_Seeker> WorkExperienceJobSeekerOfViewModel { get; set; } public IEnumerable<Job_Expertises> JobExpertisesOfViewModel { get; set; } public IEnumerable<Degrees_Work_Experience_Required> DegreesWorkExperienceRequiredOfViewModel { get; set; } public IEnumerable<Specialized_Course> SpecializedCourseOfViewModel { get; set; } public IEnumerable<Book_Published> BookPublishedOfViewModel { get; set; } public IEnumerable<Basic_Table> BasicTable { get; set; } public int NumberForm { get; set; } // EditResumes.chtml & ShowResumes.chtml ===> baraye select kardan formha :D }
[HttpGet] public ActionResult EditResumes(int id) { var contex = new Final_My_ProjectEntities2(); var res1 = contex.Resumes.Where(rec => rec.Resume_ID == id); var res2 = contex.Work_Experience_Job_Seeker.Where(rec => rec.Resume_ID == id).ToList(); var res3 = contex.Job_Expertises.Where(rec => rec.Resume_ID == id).ToList(); var res4 = contex.Degrees_Work_Experience_Required.Where(rec => rec.Resume_ID == id).ToList(); var res5 = contex.Specialized_Course.Where(rec => rec.Resume_ID == id).ToList(); var res6 = contex.Book_Published.Where(rec => rec.Resume_ID == id).ToList(); var res12 = contex.Basic_Table.ToList(); var viewModel = new ResumeViewModel(res1, res2, res3, res4, res5, res6,res12); var items = new SelectList( new[] { new {Value = "1", Text = "فرم مهارت ها"}, new {Value = "2", Text = "فرم کتاب/مقالات منتشر شده"}, new {Value = "3", Text = "فرم سابقه کاری"}, new {Value = "4", Text = "فرم دورههای تخصصی گذرانده"}, new {Value = "5", Text = "فرم تخصصهای شغلی"}, new {Value = "6", Text = "فرم مدارک تحصیلی"} }, "Value", "Text"); ViewBag.Form = new SelectList(items, "Value", "Text"); var res7 = contex.Basic_Table.Where(rec => rec.Domain == "MilitaryStatus").ToList(); ViewBag.MilitaryStatus = new SelectList(res7, "Value", "Meaning",res1); var res8 = contex.Basic_Table.Where(rec => rec.Domain == "Sex"); ViewBag.Sex = new SelectList(res8, "Value", "Meaning", res1); var res9 = contex.Basic_Table.Where(rec => rec.Domain == "MartialStatus").ToList(); ViewBag.MartialStatus = new SelectList(res9, "Value", "Meaning", res1); var res10 = contex.Basic_Table.Where(rec => rec.Domain == "Degree").ToList(); ViewBag.Degree = new SelectList(res10, "Value", "Meaning"); var res11 = contex.Basic_Table.Where(rec => rec.Domain == "Ability").ToList(); ViewBag.Ability = new SelectList(res11, "Value", "Meaning"); return View(viewModel); }
@model Final_My_Project.ViewModels.ResumeViewModel @{ ViewBag.Title = "ویرایش رزومه"; ViewBag.PartOne = "فرم مهارت ها"; ViewBag.PartTwo = "فرم کتاب/مقالات منتشر شده"; ViewBag.Part3 = "فرم سابقه کاری"; ViewBag.Part4 = "فرم دورههای تخصصی گذرانده"; ViewBag.Part5 = "فرم تخصصهای شغلی"; ViewBag.Part6 = "فرم مدارک تحصیلی"; } <h2 style="font-family: Arial;">@ViewBag.Title</h2><br/> <script type="text/javascript"> $(function () { $('#Gender').change(function () { var selectKind = $(this).find('option:selected').text(); var divMilitary; if (selectKind == "زن") { divMilitary = $('#Military'); divMilitary.hide(); divMilitary.css('display', 'none'); } else if (selectKind == "مرد") { divMilitary = $('#Military'); divMilitary.show(); divMilitary.css('display', 'block'); } }); }); </script> <script type="text/javascript"> $(function () { $('#SelectForm').change(function () { var selectFrom = $(this).find('option:selected').text(); if (selectFrom == "فرم مهارت ها") { $('#PartOne').show(); $('#PartOne').css('display', 'block'); $('#PartTwo').hide(); $('#PartTwo').css('display', 'none'); $('#Part3').hide(); $('#Part3').css('display', 'none'); $('#Part4').hide(); $('#Part4').css('display', 'none'); $('#Part5').hide(); $('#Part5').css('display', 'none'); $('#Part6').hide(); $('#Part6').css('display', 'none'); } if (selectFrom == "فرم کتاب/مقالات منتشر شده") { $('#PartTwo').show(); $('#PartTwo').css('display', 'block'); $('#PartOne').show(); $('#PartOne').css('display', 'none'); $('#Part3').hide(); $('#Part3').css('display', 'none'); $('#Part4').hide(); $('#Part4').css('display', 'none'); $('#Part5').hide(); $('#Part5').css('display', 'none'); $('#Part6').hide(); $('#Part6').css('display', 'none'); } if (selectFrom == "فرم سابقه کاری") { $('#Part3').show(); $('#Part3').css('display', 'block'); $('#PartTwo').hide(); $('#PartTwo').css('display', 'none'); $('#PartOne').show(); $('#PartOne').css('display', 'none'); $('#Part4').hide(); $('#Part4').css('display', 'none'); $('#Part5').hide(); $('#Part5').css('display', 'none'); $('#Part6').hide(); $('#Part6').css('display', 'none'); } if (selectFrom == "فرم دورههای تخصصی گذرانده") { $('#Part4').show(); $('#Part4').css('display', 'block'); $('#PartTwo').hide(); $('#PartTwo').css('display', 'none'); $('#PartOne').show(); $('#PartOne').css('display', 'none'); $('#Part3').hide(); $('#Part3').css('display', 'none'); $('#Part5').hide(); $('#Part5').css('display', 'none'); $('#Part6').hide(); $('#Part6').css('display', 'none'); } if (selectFrom == "فرم تخصصهای شغلی") { $('#Part5').show(); $('#Part5').css('display', 'block'); $('#PartTwo').hide(); $('#PartTwo').css('display', 'none'); $('#PartOne').show(); $('#PartOne').css('display', 'none'); $('#Part3').hide(); $('#Part3').css('display', 'none'); $('#Part4').hide(); $('#Part4').css('display', 'none'); $('#Part6').hide(); $('#Part6').css('display', 'none'); } if (selectFrom == "فرم مدارک تحصیلی") { $('#Part6').show(); $('#Part6').css('display', 'block'); $('#Part5').show(); $('#Part5').css('display', 'none'); $('#PartTwo').hide(); $('#PartTwo').css('display', 'none'); $('#PartOne').show(); $('#PartOne').css('display', 'none'); $('#Part3').hide(); $('#Part3').css('display', 'none'); $('#Part4').hide(); $('#Part4').css('display', 'none'); } }); }); </script> @Html.DropDownListFor(m=>m.NumberForm, (SelectList)ViewBag.Form, new { id = "SelectForm" }) @using (Html.BeginForm()) { @Html.ValidationSummary(true) <div id="PartOne" > <h3 style="font-family: Arial; color: #008080; font-weight: bold; ">@ViewBag.PartOne</h3><br/> @foreach (var item in Model.ResumeOfViewModel) { <table dir="rtl"> <tr> <td> @Html.Label("عنوان رزومه") </td> <td> @Html.TextBoxFor(model =>item.Title_Of_Resume , new {@class = "text", style = "width:100 px"}) @Html.ValidationMessageFor(model => item.Title_Of_Resume) </td> </tr> <tr> <td> <div id="Gender" > @Html.Label("نوع جنسیت") @Html.DropDownList("نوع جنسیت", new SelectList(ViewBag.Sex, "Value", "Text", item.Sex_ID == 0 ? 0 : item.Sex_ID)) @Html.ValidationMessageFor(model => item.Sex_ID) </div> </td> <td> <div > @Html.Label("وضعیت تاهل") @Html.DropDownList("وضعیت تاهل", new SelectList(ViewBag.MartialStatus, "Value", "Text", item.Martial_Status_ID == 0 ? 0 : item.Martial_Status_ID)) @Html.ValidationMessageFor(model => item.Martial_Status_ID) </div> </td> </tr> <tr id="Military" style="display: none;"> <td> @Html.Label("وضعیت نظام وظیفه") </td> <td> @Html.DropDownList("وضعیت نظام وظیفه", new SelectList(ViewBag.MilitaryStatus, "Value", "Text", item.Military_Status_ID == 0 ? 0 : item.Military_Status_ID), new { id = "Gender" }) @Html.ValidationMessageFor(model => item.Military_Status_ID) </td> </tr> <tr> <td> @Html.Label("آشنایی با رایانه") </td> <td> @Html.DropDownListFor(model => item.Knowledge_Of_Computers_ID, (SelectList)ViewBag.Ability) @Html.ValidationMessageFor(model => item.Knowledge_Of_Computers_ID) </td> </tr> <tr> <td> @Html.Label("آشنایی با امور اداری و دفتری") </td> <td> @Html.DropDownListFor(model => item.Knowledge_Administrative_and_Clerical_ID, (SelectList) ViewBag.Ability) @Html.ValidationMessageFor(model => item.Knowledge_Administrative_and_Clerical_ID) </td> </tr> <tr> <td> @Html.Label("آشنایی با زبان انگلیسی") </td> <td> @Html.DropDownListFor(model => item.Knowledge_Of_English_ID, (SelectList) ViewBag.Ability) @Html.ValidationMessageFor(model => item.Knowledge_Of_English_ID) </td> </tr> <tr> <td> @Html.Label("آشنایی با زبان عربی") </td> <td> @Html.DropDownListFor(model => item.Knowledge_Of_Arabic_ID, (SelectList) ViewBag.Ability) @Html.ValidationMessageFor(model => item.Knowledge_Of_Arabic_ID) </td> </tr> <tr> <td> @Html.Label("آشنایی با ماکروسافت آفیس") </td> <td> @Html.DropDownListFor(model =>item.Knowledge_Of_Office_ID, (SelectList) ViewBag.Ability) @Html.ValidationMessageFor(model => item.Knowledge_Of_Office_ID) </td> </tr> <tr> <td> @Html.Label("آشنایی با امور مالی و حسابداری") </td> <td> @Html.DropDownListFor(model =>item.Knowledge_Of_Finance_ID, (SelectList) ViewBag.Ability) @Html.ValidationMessageFor(model => item.Knowledge_Of_Finance_ID) </td> </tr> <tr> <td> @Html.Label("آشنایی با مدیریت") </td> <td> @Html.DropDownListFor(model =>item.Knowledge_Of_Manage_ID, (SelectList) ViewBag.Ability) @Html.ValidationMessageFor(model =>item.Knowledge_Of_Manage_ID) </td> </tr> <tr> <td> @Html.Label("گواهینامه رانندگی پایه یک") </td> <td> @Html.DropDownListFor(model =>item.Driving_license_One_ID, (SelectList) ViewBag.Ability) @Html.ValidationMessageFor(model =>item.Driving_license_One_ID) </td> </tr> <tr> <td> @Html.Label("گواهینامه رانندگی پایه دو") </td> <td> @Html.DropDownListFor(model =>item.Driving_license_Two_ID, (SelectList) ViewBag.Ability) @Html.ValidationMessageFor(model =>item.Driving_license_Two_ID) </td> </tr> <tr> <td> @Html.Label("گواهینامه رانندگی پایه موتورسیکلت") </td> <td> @Html.DropDownListFor(model =>item.Certificate_Motor_ID, (SelectList) ViewBag.Ability) @Html.ValidationMessageFor(model =>item.Certificate_Motor_ID) </td> </tr> <tr> <td> @Html.Label("ماشین شخصی") </td> <td> @Html.DropDownListFor(model =>item.Personal_Vehicle_ID, (SelectList) ViewBag.Ability) @Html.ValidationMessageFor(model =>item.Personal_Vehicle_ID) </td> </tr> <tr> <td> @Html.Label("روابط عمومی") </td> <td> @Html.DropDownListFor(model =>item.Public_Relationship_ID, (SelectList) ViewBag.Ability) @Html.ValidationMessageFor(model =>item.Public_Relationship_ID) </td> </tr> <tr> <td> @Html.Label("دیگر توانایی ها") </td> <td> @Html.EditorFor(model =>item.Etc_Ability) @Html.ValidationMessageFor(model =>item.Etc_Ability) </td> </tr> </table> } </div> <p> <input type="submit" value="Save" onclick="return confirm('از ثبت اطلاعات مطمئن هستید؟')" /> </p> } <div> @Html.ActionLink("بازگشت به مدیریت رزومه ها", "ManageOfResumes") </div> @section scripts { @Scripts.Render("~/bundles/jqueryval") }
[HttpPost] public ActionResult EditResumes(ResumeViewModel model) // model null mishe ! CHERAA??! { var contex = new Final_My_ProjectEntities2(); try { if (ModelState.IsValid) { // Code... che cody? contex.SaveChanges(); } } catch (Exception) { ViewBag.wrong = "لطفا دادههای ورودی را بررسی نمایید"; } return View(model); }
ASP.NET MVC #8
معرفی HTML Helpers
یک HTML Helper تنها یک متد است که رشتهای را بر میگرداند و این رشته میتواند حاوی هر نوع محتوای دلخواهی باشد. برای مثال میتوان از HTML Helpers برای رندر تگهای HTML، مانند img و input استفاده کرد. یا به کمک HTML Helpers میتوان ساختارهای پیچیدهتری مانند نمایش لیستی از اطلاعات دریافت شده از بانک اطلاعاتی را پیاده سازی کرد. به این ترتیب حجم کدهای تکراری تولید رابط کاربری در Viewهای برنامههای ASP.NET MVC به شدت کاهش خواهد یافت، به همراه قابلیت استفاده مجدد از متدهای الحاقی HTML Helpers در برنامههای دیگر.
HTML Helpers در ASP.NET MVC معادل کنترلهای ASP.NET Web forms هستند اما نسبت به آنها بسیار سبکتر میباشند؛ برای مثال به همراه ViewState و همچنین Event model نیستند.
ASP.NET MVC به همراه تعدادی متد HTML Helper توکار است و برای دسترسی به آنها شیء Html که وهلهای از کلاس توکار HtmlHelper میباشد، در تمام Viewها قابل استفاده است.
نحوه ایجاد یک HTML Helper سفارشی
از دات نت سه و نیم به بعد امکان توسعه اشیاء توکار فریم ورک، به کمک متدهای الحاقی (extension methods) میسر شده است. برای نوشتن یک HTML Helper نیز باید همین شیوه عمل کرد و کلاس HtmlHelper را توسعه داد. در ادامه قصد داریم یک HTML Helper را جهت رندر تگ label در صفحه ایجاد کنیم. برای این منظور پوشهی جدیدی به نام Helper را به پروژه اضافه نمائید (جهت نظم بیشتر). سپس کلاس زیر را به آن اضافه کنید:
using System;
using System.Web.Mvc;
namespace MvcApplication4.Helpers
{
public static class LabelExtensions
{
public static string MyLabel(this HtmlHelper helper, string target, string text)
{
return string.Format("<label for='{0}'>{1}</label>", target, text);
}
}
}
همانطور که ملاحظه میکنید متد Label به شکل یک متد الحاقی توسعه دهنده کلاس HtmlHelper که تنها یک رشته را بر میگرداند، تعریف شده است. اکنون برای استفاده از این متد در View دلخواهی خواهیم داشت:
@using MvcApplication4.Helpers
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
@Html.MyLabel("firstName", "First Name:")
ابتدا فضای نام مرتبط با متد الحاقی باید پیوست شود و سپس از طریق شیء Html میتوان به این متد الحاقی دسترسی پیدا کرد. اگر برنامه را اجرا کنید، این خروجی را مشاهده خواهیم کرد. چرا؟
Index
<label for='firstName'>First Name:</label>
علت این است که Razor، اطلاعات را Html encoded به مرورگر تحویل میدهد. برای تغییر این رویه باید اندکی متد الحاقی تعریف شده را تغییر داد:
using System.Web.Mvc;
namespace MvcApplication4.Helpers
{
public static class LabelExtensions
{
public static MvcHtmlString MyLabel(this HtmlHelper helper, string target, string text)
{
return MvcHtmlString.Create(string.Format("<label for='{0}'>{1}</label>", target, text));
}
}
}
تنها تغییر صورت گرفته، استفاده از MvcHtmlString بجای string معمولی است تا Razor آنرا encode نکند.
تعریف HTML Helpers سفارشی به صورت عمومی:
میتوان فضای نام MvcApplication4.Helpers این مثال را عمومی کرد. یعنی بجای اینکه بخواهیم در هر View آنرا ابتدا تعریف کنیم، یکبار آنرا همانند تعاریف اصلی یک برنامه ASP.NET MVC، عمومی معرفی میکنیم. برای این منظور فایل web.config موجود در پوشه Views را باز کنید (و نه فایل web.config قرار گرفته در ریشه اصلی برنامه). سپس فضای نام مورد نظر را در قسمت namespaces صفحات اضافه نمائید:
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="MvcApplication4.Helpers"/>
</namespaces>
به این ترتیب متدهای الحاقی تعریف شده در فضای نام MvcApplication4.Helpers، در تمام Viewهای برنامه در دسترس خواهند بود.
استفاده از کلاس TagBuilder برای تولید HTML Helpers سفارشی:
using System.Web.Mvc;
namespace MvcApplication4.Helpers
{
public static class LabelExtensions
{
public static MvcHtmlString MyNewLabel(this HtmlHelper helper, string target, string text)
{
var labelTag = new TagBuilder("label");
labelTag.MergeAttribute("for", target);
labelTag.InnerHtml = text;
return MvcHtmlString.Create(labelTag.ToString());
}
}
}
در فضای نام System.Web.Mvc، کلاسی وجود دارد به نام TagBuilder که کار تولید تگهای HTML، مقدار دهی ویژگیها و خواص آنها را بسیار ساده میکند و روش توصیه شدهای است برای تولید متدهای HTML Helper. یک نمونه از کاربرد آنرا برای بازنویسی متد MyLabel ذکر شده در اینجا ملاحظه میکنید.
شبیه به همین کلاس، کلاس دیگری به نام HtmlTextWriter در فضای نام System.Web.UI برای انجام اینگونه کارها وجود دارد.
نوشتن HTML Helpers ویژه، به کمک امکانات Razor
نوع دیگری از این متدهای کمکی، Declarative HTML Helpers نام دارند. از این جهت هم Declarative نامیده شدهاند که مستقیما درون فایلهای cshtml یا vbhtml به کمک امکانات Razor قابل تعریف هستند. تولید این نوع متدهای کمکی به این شکل نسبت به مثلا روش TagBuilder سادهتر است، چون توسط Razor به سادگی و به نحو طبیعیتری میتوان تگهای HTML و کدهای مورد نظر را با هم ترکیب کرد (این رفتار طبیعی و روان، یکی از اهداف Razor است).
به عنوان مثال، تعاریف همان کلاسهای Product و Products قسمت قبل (قسمت هفتم) را در نظر بگیرید. با همان کنترلر و View ایی که ذکر شد.
سپس برای تعریف این نوع خاص از HTML Helpers/Razor Helpers باید به این نحو عمل کرد:
الف) در ریشه پروژه یا سایت، پوشهی جدیدی به نام App_Code ایجاد کنید (دقیقا به همین نام. این پوشه، جزو پوشههای ویژه ASP.NET است).
ب) بر روی این پوشه کلیک راست کرده و گزینه Add|New Item را انتخاب کنید.
ج) در صفحه باز شده، MVC 3 Partial Page/Razor را یافته و مثلا نام ProductsList.cshtml را وارد کرده و این فایل را اضافه کنید.
د) محتوای این فایل جدید را به نحو زیر تغییر دهید:
@using MvcApplication4.Models
@helper GetProductsList(List<Product> products)
{
<ul>
@foreach (var item in products)
{
<li>@item.Name ($@item.Price)</li>
}
</ul>
}
در اینجا نحوه تعریف یک helper method مخصوص Razor را مشاهده میکنید که با کلمه @helper شروع شده است. مابقی آن هم ترکیب آشنای code و markup هستند که به کمک امکانات Razor به این شکل روان میسر شده است.
اکنون اگر Viewایی بخواهد از این اطلاعات استفاده کند تنها کافی است به نحو زیر عمل نماید:
@model List<MvcApplication4.Models.Product>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
@ProductsList.GetProductsList(@Model)
ابتدا نام فایل ذکر شده بعد نام متد کمکی تعریف شده در آن. Model هم در اینجا به لیستی از محصولات اشاره میکند.
همچنین چون در پوشه app_code قرار گرفته، تمام Viewها به اطلاعات آن دسترسی خواهند داشت. علت هم این است که ASP.NET به صورت خودکار محتوای این پوشه ویژه را همواره کامپایل میکند و در اختیار برنامه قرار میدهد.
به علاوه در این فایل ProductsList.cshtml، باز هم میتوان متدهای helper دیگری را اضافه کرد و از این بابت محدودیتی ندارد. همچنین میتوان این متد helper را مستقیما داخل یک View هم تعریف کرد. بدیهی است در این حالت قابلیت استفاده مجدد از آنرا به همراه داشتن Viewهایی تمیز و کم حجم، از دست خواهیم داد.
جهت تکمیل بحث
Turn your Razor helpers into reusable libraries
روشهای تولید اعداد یا رشته تصادفی:
1- معمولترین روش تولید یک کد شش رقمی با استفاده از کلاس random
[TestMethod] public void TestRandomClass() { var code = new Random().Next(100000, 999999); Assert.IsTrue(code.ToString().Length == 6); }
[TestMethod] public void TestRandomWithEnumerable() { var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; var random = new Random(); var result = new string( Enumerable.Repeat(chars, 6) .Select(s => s[random.Next(s.Length)]) .ToArray()); Assert.IsTrue(result.Length == 6); }
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuwxyz~!@#$%^&*";
Guid.NewGuid().ToString().Replace("-", string.Empty).Substring(0, 6);
Guid.NewGuid().ToString("n").Substring(0, 6);
public static string GetUniqueKey(int maxSize) { char[] chars = new char[62]; chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray(); byte[] data = new byte[1]; using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider()) { crypto.GetNonZeroBytes(data); data = new byte[maxSize]; crypto.GetNonZeroBytes(data); } StringBuilder result = new StringBuilder(maxSize); foreach (byte b in data) { result.Append(chars[b % (chars.Length)]); } return result.ToString(); } }
5 - استفاده از متد Path.GetRandomFileName
public string Get8CharacterRandomString() { string path = Path.GetRandomFileName(); path = path.Replace(".", ""); // Remove period. return path.Substring(0, 6); // Return 6 character string }
var chars = "abcdefghijklmnopqrstuvwxyz123456789".ToArray(); string pw = Enumerable.Range(0, passwordLength) .Aggregate( new StringBuilder(), (sb, n) => sb.Append((chars[random.Next(chars.Length)])), sb => sb.ToString());