خیلی ممنون از بابت مقالات آموزشی خوبتون.
فقط سوالی برای من تو این بخش سوم پیش اومد و اون هم اینکه بعد از تعریف کلاس abstract تعریف کلاسهای rectangle و square به چه شکل شد؟ لطفا کد اون کلاسها رو هم اضافه کنید.
با تشکر
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Data.Entity.ModelConfiguration; namespace EF_General.Models.Ex18 { public class UserProfile { public int UserProfileId { set; get; } public string UserName { set; get; } [ForeignKey("CartableId")] public virtual Cartable Cartable { set; get; } // one-to-zero-or-one public int? CartableId { set; get; } public virtual ICollection<Doc> Docs { set; get; } // one-to-many } public class Doc { public int DocId { set; get; } public string Title { set; get; } public string Body { set; get; } [ForeignKey("UserProfileId")] public virtual UserProfile UserProfile { set; get; } public int UserProfileId { set; get; } public virtual ICollection<Cartable> Cartables { set; get; } // many-to-many } public class Cartable { public int CartableId { set; get; } [ForeignKey("UserProfileId")] public virtual UserProfile UserProfile { set; get; } public int UserProfileId { set; get; } public virtual ICollection<Doc> Docs { set; get; } // many-to-many } public class UserProfileMap : EntityTypeConfiguration<UserProfile> { public UserProfileMap() { this.HasOptional(x => x.Cartable) .WithRequired(x => x.UserProfile) .WillCascadeOnDelete(); } } public class MyContext : DbContext { public DbSet<UserProfile> UserProfiles { get; set; } public DbSet<Doc> Docs { get; set; } public DbSet<Cartable> Cartables { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new UserProfileMap()); base.OnModelCreating(modelBuilder); } } public class Configuration : DbMigrationsConfiguration<MyContext> { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } } public static class Test { public static void RunTests() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>()); using (var context = new MyContext()) { var user = context.UserProfiles.Find(1); if (user != null) Console.WriteLine(user.UserName); } } } }
Introducing FOREIGN KEY constraint 'FK_DocCartables_Cartables_Cartable_CartableId' on table 'DocCartables' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Could not create constraint. See previous errors.
public int? UserProfileId { set; get; }
The referential relationship will result in a cyclical reference that is not allowed. [ Constraint name = ]
<ul class="list-group"> <li class="list-group-item">Item 1</li> <li class="list-group-item">Item 2</li> <li class="list-group-item">Item 3</li> </ul>
{ "version": "1.0.0-*", "dependencies": { "NETStandard.Library": "1.6.0", "Microsoft.AspNetCore.Http.Extensions": "1.0.0", "Microsoft.AspNetCore.Mvc.Abstractions": "1.0.1", "Microsoft.AspNetCore.Mvc.Core": "1.0.1", "Microsoft.AspNetCore.Mvc.ViewFeatures": "1.0.1", "Microsoft.AspNetCore.Razor.Runtime": "1.0.0" }, "frameworks": { "netstandard1.6": { "imports": "dnxcore50" } } }
namespace Core1RtmEmptyTest.TagHelpers { [HtmlTargetElement("list-group")] public class ListGroupTagHelper : TagHelper { [HtmlAttributeName("asp-items")] public List<string> Items { get; set; } public override void Process(TagHelperContext context, TagHelperOutput output) { } } }
<list-group></list-group>
<list-group asp-items="Model.Items"></list-group>
using System; using System.Collections.Generic; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; namespace Core1RtmEmptyTest.TagHelpers { [HtmlTargetElement("list-group")] public class ListGroupTagHelper : TagHelper { [HtmlAttributeName("asp-items")] public List<string> Items { get; set; } protected HttpRequest Request => ViewContext.HttpContext.Request; [ViewContext, HtmlAttributeNotBound] public ViewContext ViewContext { get; set; } public override void Process(TagHelperContext context, TagHelperOutput output) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (output == null) { throw new ArgumentNullException(nameof(output)); } if (Items == null) { throw new InvalidOperationException($"{nameof(Items)} must be provided"); } output.TagName = "ul"; output.TagMode = TagMode.StartTagAndEndTag; output.Attributes.Add("class", "list-group"); foreach (var item in Items) { TagBuilder itemBuilder = new TagBuilder("li"); itemBuilder.AddCssClass("list-group-item"); itemBuilder.InnerHtml.Append(item); output.Content.AppendHtml(itemBuilder); } } } }
@addTagHelper *,Core1RtmEmptyTest.TagHelpers
Lazy<ExpensiveResource> ownedResource = new Lazy<ExpensiveResource>();
public Lazy(bool isThreadSafe);
public Lazy(Func<T> valueFactory, bool isThreadSafe);
using System;
using System.Threading;
namespace LazyExample
{
class Program
{
static void Main()
{
Console.WriteLine("Before assignment");
var slow = new Lazy<Slow>();
Console.WriteLine("After assignment");
Thread.Sleep(1000);
Console.WriteLine(slow);
Console.WriteLine(slow.Value);
Console.WriteLine("Press a key...");
Console.Read();
}
}
class Slow
{
public Slow()
{
Console.WriteLine("Start creation");
Thread.Sleep(2000);
Console.WriteLine("End creation");
}
}
}
Before assignment
After assignment
Value is not created.
Start creation
End creation
LazyExample.Slow
Press a key...
using System;
namespace LazySample
{
class Program
{
static void Main()
{
var expensiveClass =
new Lazy<ExpensiveClass>
(
() =>
{
var fobj = new ExpensiveClass
{
SomeProperty = 100
};
return fobj;
}
);
Console.WriteLine("expensiveClass has value yet {0}",
expensiveClass.IsValueCreated);
Console.WriteLine("expensiveClass.SomeProperty value {0}",
(expensiveClass.Value).SomeProperty);
Console.WriteLine("expensiveClass has value yet {0}",
expensiveClass.IsValueCreated);
Console.WriteLine("Press a key...");
Console.Read();
}
}
class ExpensiveClass
{
public int SomeProperty { get; set; }
public ExpensiveClass()
{
Console.WriteLine("ExpensiveClass constructed");
}
}
}
import 'dart:html'; import 'dart:math' show Random; import 'dart:convert' show JSON;
class PirateName { ... PirateName.fromJSON(String jsonString) { Map storedName = JSON.decode(jsonString); _firstName = storedName['f']; _appellation = storedName['a']; } }
class PirateName { ... String get jsonString => JSON.encode({"f": _firstName, "a": _appellation}); }
final String TREASURE_KEY = 'pirateName'; void main() { ... }
void setBadgeName(PirateName newName) { if (newName == null) { return; } querySelector('#badgeName').text = newName.pirateName; window.localStorage[TREASURE_KEY] = newName.jsonString; }
void setBadgeName(PirateName newName) { ... } PirateName getBadgeNameFromStorage() { String storedName = window.localStorage[TREASURE_KEY]; if (storedName != null) { return new PirateName.fromJSON(storedName); } else { return null; } }
void main() { ... setBadgeName(getBadgeNameFromStorage()); }
{ "names": [ "Anne", "Bette", "Cate", "Dawn", "Elise", "Faye", "Ginger", "Harriot", "Izzy", "Jane", "Kaye", "Liz", "Maria", "Nell", "Olive", "Pat", "Queenie", "Rae", "Sal", "Tam", "Uma", "Violet", "Wilma", "Xana", "Yvonne", "Zelda", "Abe", "Billy", "Caleb", "Davie", "Eb", "Frank", "Gabe", "House", "Icarus", "Jack", "Kurt", "Larry", "Mike", "Nolan", "Oliver", "Pat", "Quib", "Roy", "Sal", "Tom", "Ube", "Val", "Walt", "Xavier", "Yvan", "Zeb"], "appellations": [ "Awesome", "Captain", "Even", "Fighter", "Great", "Hearty", "Jackal", "King", "Lord", "Mighty", "Noble", "Old", "Powerful", "Quick", "Red", "Stalwart", "Tank", "Ultimate", "Vicious", "Wily", "aXe", "Young", "Brave", "Eager", "Kind", "Sandy", "Xeric", "Yellow", "Zesty"]}
... <div> <input type="text" id="inputName" maxlength="15" disabled> </div> <div> <button id="generateButton" disabled>Aye! Gimme a name!</button> </div> ...
import 'dart:html'; import 'dart:math' show Random; import 'dart:convert' show JSON; import 'dart:async' show Future;
class PirateName { ... static List<String> names = []; static List<String> appellations = []; ... }
class PirateName { ... static Future readyThePirates() { var path = 'piratenames.json'; return HttpRequest.getString(path) .then(_parsePirateNamesFromJSON); } static _parsePirateNamesFromJSON(String jsonString) { Map pirateNames = JSON.decode(jsonString); names = pirateNames['names']; appellations = pirateNames['appellations']; } }
SpanElement badgeNameElement; void main() { ... }
void main() { InputElement inputField = querySelector('#inputName'); inputField.onInput.listen(updateBadge); genButton = querySelector('#generateButton'); genButton.onClick.listen(generateBadge); badgeNameElement = querySelector('#badgeName'); ... }
void main() { ... PirateName.readyThePirates() .then((_) { //on success inputField.disabled = false; //enable genButton.disabled = false; //enable setBadgeName(getBadgeNameFromStorage()); }) .catchError((arrr) { print('Error initializing pirate names: $arrr'); badgeNameElement.text = 'Arrr! No names.'; }); }
زمانی که سیستم عامل های GUI مثل ویندوز به بازار آمدند، یکی از قسمتهای گرافیکی آنها AddressBar نام داشت که مسیر حرکت آنها را در فایل سیستم نشان میداد و در سیستم عاملهای متنی CLI با دستور cd یا pwd انجام میشد. بعدها در وب هم همین حرکت با نام BreadCrumb صورت گرفت که به عنوان مثال مسیر رسیدن به صفحهی یک محصول یا یک مقاله را نشان میداد. در یک پروژهی اندرویدی نیاز بود تا یک ساختار درختی را پیاده سازی کنم، ولی در برنامههای اندروید ایجاد یک درخت، کار هوشمندانه و مطلوبی نیست و روش کار به این صورت است که یک لیست از گروههای والد را نمایش داده و با انتخاب هر آیتم لیست به آیتمهای فرزند تغییر میکند. حالا مسئله این بود که کاربر باید مسیر حرکت خودش را بشناسد. به همین علت مجبور شدم یک BreadCrumb را برای آن طراحی کنم که در زیر تصویر آن را مشاهده میکنید.
از نکات جالب توجه در مورد این ماژول میتوان گفت که قابلیت این را دارد تا تصمیمات خود را بر اساس اندازههای مختلف صفحه نمایش بگیرد. به عنوان مثال اگر آیتمهای بالا بیشتر از سه عدد باشد و در صفحه جا نشود از یک مسیر جعلی استفاده میکند و همهی آیتمها با اندیس شماره 1 تا index-3 را درون یک آیتم با عنوان (...) قرار میدهد که من به آن میگویم مسیر جعلی. به عنوان نمونه مسیر تصویر بالا در صفحه جا شده است و نیازی به این کار دیده نشده است. ولی تصویر زیر از آن جا که مسیر، طول width صفحه نمایش رد کرده است، نیاز است تا چنین کاری انجام شود. موقعیکه کاربر آیتم ... را کلیک کند، مسیر باز شده و به محل index-3 حرکت میکند. یعنی دو مرحله به عقب باز میگردد.
نگاهی به کارکرد ماژول
قبل از توضیح در مورد سورس، اجازه دهید نحوهی استفاده از آن را ببینیم.
این سورس شامل دو کلاس است که سادهترین کلاس آن AndBreadCrumbItem میباشد که مشابه کلاس ListItem در بخش وب دات نت است و دو مقدار، یکی متن و دیگری Id را میگیرد:
سورس:
public class AndBreadCrumbItem { private int Id; private String diplayText; public AndBreadCrumbItem(int Id, String displayText) { this.Id=Id; this.diplayText=displayText; } public String getDiplayText() { return diplayText; } public void setDiplayText(String diplayText) { this.diplayText = diplayText; } public int getId() { return Id; } public void setId(int id) { Id = id; } }
به عنوان مثال میخواهیم یک breadcrumb را با مشخصات زیر بسازیم:
AndBreadCrumbItem itemhome=new AndBreadCrumbItem(0,"Home"); AndBreadCrumbItem itemproducts=new AndBreadCrumbItem(12,"Products"); AndBreadCrumbItem itemdigital=new AndBreadCrumbItem(15,"Digital"); AndBreadCrumbItem itemhdd=new AndBreadCrumbItem(56,"Hard Disk Drive");
AndBreadCrumb breadCrumb=new AndBreadCrumb(this); breadCrumb.AddNewItem(itemhome); breadCrumb.AddNewItem(itemproducts); breadCrumb.AddNewItem(itemdigital); breadCrumb.AddNewItem(itemhdd);
breadCumb.setContext(this);
پس از افزودن آیتم ها، تنظیمات زیر را اعمال نمایید:
LinearLayout layout=(LinearLayout)getActivity().findViewById(R.id.breadcumblayout); layout.setPadding(8, 8, 8, 8); breadCrumb.setLayout(layout); breadCrumb.SetTinyNextNodeImage(R.drawable.arrow); breadCrumb.setTextSize(25); breadCrumb.SetViewStyleId(R.drawable.list_item_style);
breadCrumb.UpdatePath();
breadCrumb.setRTL(true);
در صورتیکه قصد دارید تنظیمات بیشتری چون رنگ متن، فونت متن و ... را روی هر المان اعمال کنید، از رویداد زیر استفاده کنید:
breadCrumb.setOnTextViewUpdate(new ITextViewUpdate() { @Override public TextView UpdateTextView(Context context, TextView tv) { tv.setTextColor(...); tv.setTypeface(...); return tv; } });
breadCumb.setOnClickListener(new IClickListener() { @Override public void onClick(int position, int Id) { //... } });
آخرین متد موجود که کمترین استفاده را دارد، متد SetNoResize است. در صورتیکه این متد با True مقداردهی گردد، عملیات تنظیم بر اساس صفحهی نمایش لغو میشود. این متد برای زمانی مناسب است که به عنوان مثال شما از یک HorozinalScrollView استفاده کرده باشید. در این حالت layout شما هیچ گاه به پایان نمیرسد و بهتر هست عملیات اضافه را لغو کنید.
نگاهی به سورس
کلاس زیر شامل بخشهای زیر است:
//=-=--=-=-=-=-=-=-=-=-=-=-=- Private Properties -=-=-=-=-=-=-=--=-=-= private List<AndBreadCrumbItem> items=null; private List<TextView> textViews; private int tinyNextNodeImage; private int viewStyleId; private Context context; private boolean RTL; private float textSize=20; private boolean noResize=false; LinearLayout layout; IClickListener clickListener; ITextViewUpdate textViewUpdate; LinearLayout.LayoutParams params ;
//=-=---=-=-=-=-- Constructor =--=-=-=-=-=--=-=- public AndBreadCrumb(Context context) { this.context=context; params = new LinearLayout.LayoutParams (LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); } //=-=-=--=--=-=-=-=-=-=-=-=- Public Properties --=-=-=-=-=-=--=-=-=-=-=-=- //each category would be added to create path public void AddNewItem(AndBreadCrumbItem item) { if(items==null) items=new ArrayList<>(); items.add(item); } // if you want a pointer or next node between categories or textviews public void SetTinyNextNodeImage(int resId) {this.tinyNextNodeImage=resId;} public void SetViewStyleId(int resId) {this.viewStyleId=resId;} public void setTextSize(float textSize) {this.textSize = textSize;} public boolean isRTL() { return RTL; } public void setRTL(boolean RTL) { this.RTL = RTL; } public void setLayout(LinearLayout layout) { this.layout = layout; } public void setContext(Context context) { this.context = context; } public boolean isNoResize() { return noResize; } public void setNoResize(boolean noResize) { this.noResize = noResize; }
//primary method for render objects on layout private void DrawPath() { //stop here if essentail elements aren't present if (items == null) return ; if (layout == null) return; if (items.size() == 0) return; //we need to get size of layout,so we use the post method to run this thread when ui is ready layout.post(new Runnable() { @Override public void run() { //textviews created here one by one int position = 0; textViews = new ArrayList<>(); for (AndBreadCrumbItem item : items) { TextView tv = MakeTextView(position, item.getId()); tv.setText(item.getDiplayText()); textViews.add(tv); position++; } //add textviews on layout AddTextViewsOnLayout(); //we dont manage resizing anymore if(isNoResize()) return; //run this code after textviews Added to get widths of them TextView last_tv=textViews.get(textViews.size()-1); last_tv.post(new Runnable() { @Override public void run() { //define width of each textview depend on screen width BatchSizeOperation(); } }); } }); }
TextView tv=new TextView(this); tv.getWidth(); //return 0 layout.add(tv); tv.getWidth(); //return 0
TextView tv=new TextView(this); tv.post(new Runnable() { @Override public void run() { tv.getWidth(); //return x } });
private TextView MakeTextView(final int position, final int Id) { //settings for cumbs TextView tv=new TextView(this.context); tv.setEllipsize(TextUtils.TruncateAt.END); tv.setSingleLine(true); tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); tv.setBackgroundResource(viewStyleId); /*call custom event - this event will be fired when user click on one of textviews and returns position of textview and value that user sat as id */ tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SetPosition(position); clickListener.onClick(position, Id); } }); //if user wants to update each textviews if(textViewUpdate!=null) tv=textViewUpdate.UpdateTextView(context,tv); if(isRTL()) tv.setRotationY(180); return tv; }
//this method calling by everywhere to needs add textviews on the layout like master method :drawpath private void AddTextViewsOnLayout() { //prepare layout //remove everything on layout for recreate it layout.removeAllViews(); layout.setOrientation(LinearLayout.HORIZONTAL); layout.setVerticalGravity(Gravity.CENTER_VERTICAL); if(isRTL()) layout.setRotationY(180); //add textviews one by one int position=0; for (TextView tv:textViews) { layout.addView(tv,params); //add next node image between textviews if user defined a next node image if(tinyNextNodeImage>0) if(position<(textViews.size()-1)) { layout.addView(GetNodeImage(), params); position++; } } }
//set textview width depend on screen width private void BatchSizeOperation() { //get width of next node between cumbs Bitmap tinyBmap = BitmapFactory.decodeResource(context.getResources(), tinyNextNodeImage); int tinysize=tinyBmap.getWidth(); //get sum of nodes tinysize*=(textViews.size()-1); ... }
//get width size of screen(layout is screen here) int screenWidth=GetLayoutWidthSize(); //get sum of arrows and cumbs width int sumtvs=tinysize; for (TextView tv : textViews) { int width=tv.getWidth(); sumtvs += width; }
private int GetLayoutWidthSize() { int width=layout.getWidth(); int padding=layout.getPaddingLeft()+layout.getPaddingRight(); width-=padding; return width; }
private void BatchSizeOperation() { .... //if sum of cumbs is less than screen size the state is good so return same old textviews if(sumtvs<screenWidth) return ; if(textViews.size()>3) { //make fake path MakeFakePath(); //clear layout and add textviews again AddTextViewsOnLayout(); } //get free space without next nodes -> and spilt rest of space to textviews count to get space for each textview int freespace =screenWidth-tinysize; int each_width=freespace/textViews.size(); //some elements have less than each_width,so we should leave size them and calculate more space again int view_count=0; for (TextView tv:textViews) { if (tv.getWidth()<=each_width) freespace=freespace-tv.getWidth(); else view_count++; } if (view_count==0) return; each_width=freespace/view_count; for (TextView tv:textViews) { if (tv.getWidth()>each_width) tv.setWidth(each_width); } }
//if elements are so much(mor than 3),we make a fake path to decrease elements private void MakeFakePath() { //we make 4 new elements that index 1 is fake element and has a rest of real path in its heart //when user click on it,path would be opened textViews=new ArrayList<>(4); TextView[] tvs=new TextView[4]; int[] positions= {0,items.size()-3,items.size()-2,items.size()-1}; for (int i=0;i<4;i++) { //request for new textviews tvs[i]=MakeTextView(positions[i],items.get(positions[i]).getId()); if(i!=1) tvs[i].setText(items.get(positions[i]).getDiplayText()); else { tvs[i].setText("..."); //override click event and change it to part of code to open real path by call setposition method and redraw path tvs[i].setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int pos = items.size() - 3; int id = items.get(pos).getId(); SetPosition(items.size() - 3); clickListener.onClick(pos, id); } }); } textViews.add(tvs[i]); } }
double d = 0; Console.WriteLine(1 / d); // compiles, runs, results in: Infinity
اگر در حال تهیه یک سایت چند زبانه هستید و همچنین سری مقالات Globalization در ASP.NET MVC رو دنبال کرده باشید میدانید که با تغییر Culture فایلهای Resource مورد نظر بارگذاری و نوشتههای سایت تغییر میابند ولی با تغییر Culture رفتار اعتبارسنجی در سمت سرور نیز تغییر و اعتبارسنجی بر اساس Culture فعلی سایت انجام میگیرد. بررسی این موضوع را با یک مثال شروع میکنیم.
یک پروژه وب بسازید سپس به پوشه Models یک کلاس با نام ValueModel اضافه کنید. تعریف کلاس به شکل زیر هست:
public class ValueModel { [Required] [Display(Name = "Decimal Value")] public decimal DecimalValue { get; set; } [Required] [Display(Name = "Double Value")] public double DoubleValue { get; set; } [Required] [Display(Name = "Integer Value")] public int IntegerValue { get; set; } [Required] [Display(Name = "Date Value")] public DateTime DateValue { get; set; } }
به سراغ کلاس HomeController بروید و کدهای زیر را اضافه کنید:
[HttpPost] public ActionResult Index(ValueModel valueModel) { if (ModelState.IsValid) { return Redirect("Index"); } return View(valueModel); }
Culture را به fa-IR تغییر میدهیم، برای اینکار در فایل web.config در بخش system.web کد زیر اضافه نمایید:
<globalization culture="fa-IR" uiCulture="fa-IR" />
و در نهایت به سراغ فایل Index.cshtml بروید کدهای زیر رو اضافه کنید:
@using (Html.BeginForm()) { <ol> <li> @Html.LabelFor(m => m.DecimalValue) @Html.TextBoxFor(m => m.DecimalValue) @Html.ValidationMessageFor(m => m.DecimalValue) </li> <li> @Html.LabelFor(m => m.DoubleValue) @Html.TextBoxFor(m => m.DoubleValue) @Html.ValidationMessageFor(m => m.DoubleValue) </li> <li> @Html.LabelFor(m => m.IntegerValue) @Html.TextBoxFor(m => m.IntegerValue) @Html.ValidationMessageFor(m => m.IntegerValue) </li> <li> @Html.LabelFor(m => m.DateValue) @Html.TextBoxFor(m => m.DateValue) @Html.ValidationMessageFor(m => m.DateValue) </li> <li> <input type="submit" value="Submit"/> </li> </ol> }
پرژه را اجرا نمایید و در ٢ تکست باکس اول ٢ عدد اعشاری را و در ٢ تکست باکس آخر یک عدد صحیح و یک تاریخ وارد نمایید و سپس دکمه Submit را بزنید. پس از بازگشت صفحه از سمت سرور در در ٢ تکست باکس اول با این پیامها روبرو میشوید که مقادیر وارد شده نامعتبر میباشند.
اگر پروژه رو در حالت دیباگ اجرا کنیم و نگاهی به داخل ModelState بیاندازیم، میبینیم که کاراکتر جدا کننده قسمت اعشاری برای fa-IR '/' میباشد که در اینجا برای اعداد مورد نظر کاراکتر '.' وارد شده است.
برای فایق شدن بر این مشکل یا باید سمت سرور اقدام کرد یا در سمت کلاینت. در بخش اول راه حل سمت کلاینت را بررسی مینماییم.
در سمت کلاینت برای اینکه کاربر را مجبور به وارد کردن کاراکترهای مربوط به Culture فعلی سایت نماییم باید مقادیر وارد شده را اعتبارسنجی و در صورت معتبر نبودن مقادیر پیام مناسب نشان داده شود. برای اینکار از کتابخانه jQuery Globalize استفاده میکنیم. برای اضافه کردن jQuery Globalize از طریق کنسول nuget فرمان زیر اجرا نمایید:
PM> Install-Package jquery-globalize
پس از نصب کتابخانه اگر به پوشه Scripts نگاهی بیاندازید میبینید که پوشەای با نام jquery.globalize اضافه شده است. درداخل پوشه زیر پوشەی دیگری با نام cultures وجود دارد که در آن Cultureهای مختلف وجود دارد و بسته به نیاز میتوان از آنها استفاده کرد. دوباره به سراغ فایل Index.cshtm بروید و فایلهای جاوا اسکریپتی زیر را به صفحه اضافه کنید:
<script src="~/Scripts/jquery.validate.js"> </script> <script src="~/Scripts/jquery.validate.unobtrusive.js"> </script> <script src="~/Scripts/jquery.globalize/globalize.js"> </script> <script src="~/Scripts/jquery.globalize/cultures/globalize.culture.fa-IR.js"> </script>
در فایل globalize.culture.fa-IR.js کاراکتر جدا کننده اعشاری '.' در نظر گرفته شده است که مجبور به تغییر آن هسیتم. برای اینکار فایل را باز کرده و numberFormat را پیدا کنید و آن را به شکل زیر تغییر دهید:
numberFormat: { pattern: ["n-"], ".": "/", currency: { pattern: ["$n-", "$ n"], ".": "/", symbol: "ریال" } },
و در نهایت کدهای زیر را به فایل Index.cshtml اضافه کنید و برنامه را دوباره اجرا نمایید:
Globalize.culture('fa-IR'); $.validator.methods.number = function(value, element) { if (value.indexOf('.') > 0) { return false; } var splitedValue = value.split('/'); if (splitedValue.length === 1) { return !isNaN(Globalize.parseInt(value)); } else if (splitedValue.length === 2 && $.trim(splitedValue[1]).length === 0) { return false; } return !isNaN(Globalize.parseFloat(value)); }; };
در خط اول Culture را ست مینمایم و در ادامه نحوه اعتبارسنجی را در unobtrusive validation تغییر میدهیم. از آنجایی که برای اعتبارسنجی عدد وارد شده از تابع parseFloat استفاده میشود، کاراکتر جدا کننده قسمت اعشاری قابل قبول برای این تابع '.' است پس در داخل تابع دوباره '/' به '.' تبدیل میشود و سپس اعتبارسنجی انجام میشود از اینرو اگر کاربر '.' را نیز وارد نماید قابل قبول است به همین دلیل با این خط کد if (value.indexOf('.') > 0) وجود نقطه را بررسی میکنیم تا در صورت وجود '.' پیغام خطا نشان داده شود.در خط بعدی بررسی مینماییم که اگر عدد وارد شده اعشاری نباشد از تابع parseInt استفاده نماییم. در خط بعدی این حالت را بررسی مینماییم که اگر کاربر عددی همچون /١٢ وارد کرد پیغام خطا صادر شود.
برای اعتبارسنجی تاریخ شمسی متاسفانه توابع کمکی برای تبدیل تاریخ در فایل globalize.culture.fa-IR.js وجود ندارد ولی اگر نگاهی به فایلهای Culture عربی بیاندازید همه دارای توابع کمکی برای تبدیل تاریج هجری به میلادی هستند به همین دلیل امکان اعتبارسنجی تاریخ شمسی با استفاده از jQuery Globalize میسر نمیباشد. من خودم تعدادی توابع کمکی را به globalize.culture.fa-IR.js اضافه کردەام که از تقویم فارسی آقای علی فرهادی برداشت شده است و با آنها کار اعتبارسنجی را انجام میدهیم. لازم به ذکر است این روش ١٠٠% تست نشده است و شاید راه کاملا اصولی نباشد ولی به هر حال در اینجا توضیح میدهم. در فایل globalize.culture.fa-IR.js قسمت Gregorian_Localized را پیدا کنید و آن را با کدهای زیر جایگزین کنید:
Gregorian_Localized: { firstDay: 6, days: { names: ["یکشنبه", "دوشنبه", "سه شنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه"], namesAbbr: ["یکشنبه", "دوشنبه", "سه شنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه"], namesShort: ["ی", "د", "س", "چ", "پ", "ج", "ش"] }, months: { names: ["ژانویه", "فوریه", "مارس", "آوریل", "می", "ژوئن", "ژوئیه", "اوت", "سپتامبر", "اُکتبر", "نوامبر", "دسامبر", ""], namesAbbr: ["ژانویه", "فوریه", "مارس", "آوریل", "می", "ژوئن", "ژوئیه", "اوت", "سپتامبر", "اُکتبر", "نوامبر", "دسامبر", ""] }, AM: ["ق.ظ", "ق.ظ", "ق.ظ"], PM: ["ب.ظ", "ب.ظ", "ب.ظ"], patterns: { d: "yyyy/MM/dd", D: "yyyy/MM/dd", t: "hh:mm tt", T: "hh:mm:ss tt", f: "yyyy/MM/dd hh:mm tt", F: "yyyy/MM/dd hh:mm:ss tt", M: "dd MMMM" }, JalaliDate: { g_days_in_month: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], j_days_in_month: [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29] }, gregorianToJalali: function (gY, gM, gD) { gY = parseInt(gY); gM = parseInt(gM); gD = parseInt(gD); var gy = gY - 1600; var gm = gM - 1; var gd = gD - 1; var gDayNo = 365 * gy + parseInt((gy + 3) / 4) - parseInt((gy + 99) / 100) + parseInt((gy + 399) / 400); for (var i = 0; i < gm; ++i) gDayNo += Globalize.culture().calendars.Gregorian_Localized.JalaliDate.g_days_in_month[i]; if (gm > 1 && ((gy % 4 == 0 && gy % 100 != 0) || (gy % 400 == 0))) /* leap and after Feb */ ++gDayNo; gDayNo += gd; var jDayNo = gDayNo - 79; var jNp = parseInt(jDayNo / 12053); jDayNo %= 12053; var jy = 979 + 33 * jNp + 4 * parseInt(jDayNo / 1461); jDayNo %= 1461; if (jDayNo >= 366) { jy += parseInt((jDayNo - 1) / 365); jDayNo = (jDayNo - 1) % 365; } for (var i = 0; i < 11 && jDayNo >= Globalize.culture().calendars.Gregorian_Localized.JalaliDate.j_days_in_month[i]; ++i) { jDayNo -= Globalize.culture().calendars.Gregorian_Localized.JalaliDate.j_days_in_month[i]; } var jm = i + 1; var jd = jDayNo + 1; return [jy, jm, jd]; }, jalaliToGregorian: function (jY, jM, jD) { jY = parseInt(jY); jM = parseInt(jM); jD = parseInt(jD); var jy = jY - 979; var jm = jM - 1; var jd = jD - 1; var jDayNo = 365 * jy + parseInt(jy / 33) * 8 + parseInt((jy % 33 + 3) / 4); for (var i = 0; i < jm; ++i) jDayNo += Globalize.culture().calendars.Gregorian_Localized.JalaliDate.j_days_in_month[i]; jDayNo += jd; var gDayNo = jDayNo + 79; var gy = 1600 + 400 * parseInt(gDayNo / 146097); /* 146097 = 365*400 + 400/4 - 400/100 + 400/400 */ gDayNo = gDayNo % 146097; var leap = true; if (gDayNo >= 36525) /* 36525 = 365*100 + 100/4 */ { gDayNo--; gy += 100 * parseInt(gDayNo / 36524); /* 36524 = 365*100 + 100/4 - 100/100 */ gDayNo = gDayNo % 36524; if (gDayNo >= 365) gDayNo++; else leap = false; } gy += 4 * parseInt(gDayNo / 1461); /* 1461 = 365*4 + 4/4 */ gDayNo %= 1461; if (gDayNo >= 366) { leap = false; gDayNo--; gy += parseInt(gDayNo / 365); gDayNo = gDayNo % 365; } for (var i = 0; gDayNo >= Globalize.culture().calendars.Gregorian_Localized.JalaliDate.g_days_in_month[i] + (i == 1 && leap) ; i++) gDayNo -= Globalize.culture().calendars.Gregorian_Localized.JalaliDate.g_days_in_month[i] + (i == 1 && leap); var gm = i + 1; var gd = gDayNo + 1; return [gy, gm, gd]; }, checkDate: function (jY, jM, jD) { return !(jY < 0 || jY > 32767 || jM < 1 || jM > 12 || jD < 1 || jD > (Globalize.culture().calendars.Gregorian_Localized.JalaliDate.j_days_in_month[jM - 1] + (jM == 12 && !((jY - 979) % 33 % 4)))); }, convert: function (value, format) { var day, month, year; var formatParts = format.split('/'); var dateParts = value.split('/'); if (formatParts.length !== 3 || dateParts.length !== 3) { return false; } for (var j = 0; j < formatParts.length; j++) { var currentFormat = formatParts[j]; var currentDate = dateParts[j]; switch (currentFormat) { case 'dd': if (currentDate.length === 2 || currentDate.length === 1) { day = currentDate; } else { year = currentDate; } break; case 'MM': month = currentDate; break; case 'yyyy': if (currentDate.length === 4) { year = currentDate; } else { day = currentDate; } break; default: return false; } } year = parseInt(year); month = parseInt(month); day = parseInt(day); var isValidDate = Globalize.culture().calendars.Gregorian_Localized.checkDate(year, month, day); if (!isValidDate) { return false; } var grDate = Globalize.culture().calendars.Gregorian_Localized.jalaliToGregorian(year, month, day); var shDate = Globalize.culture().calendars.Gregorian_Localized.gregorianToJalali(grDate[0], grDate[1], grDate[2]); if (year === shDate[0] && month === shDate[1] && day === shDate[2]) { return true; } return false; } },
روال کار در تابع convert به اینصورت است که ابتدا تاریخ وارد شده را بررسی مینماید تا معتبر بودن آن معلوم شود به عنوان مثال اگر تاریخی مثل 1392/12/31 وارد شده باشد و در ادامه برای بررسی بیشتر تاریخ یک بار به میلادی و تاریخ میلادی دوباره به شمسی تبدیل میشود و با تاریخ وارد شده مقایسه میشود و در صورت برابری تاریخ معتبر اعلام میشود. در فایل Index.cshtml کدهای زیر اضافی نمایید:
$.validator.methods.date = function (value, element) { return Globalize.culture().calendars.Gregorian_Localized.convert(value, 'yyyy/MM/dd'); };
برای اعتبارسنجی تاریخ میتوانید از ٢ فرمت استفاده کنید:
١ – yyyy/MM/dd
٢ – dd/MM/yyyy
البته از توابع اعتبارسنجی تاریخ میتوانید به صورت جدا استفاده نمایید و لزومی ندارد آنها را همراه با jQuery Globalize بکار ببرید. در آخر خروجی کار به این شکل است:
در کل استفاده از jQuery Globalize برای اعتبارسنجی در سایتهای چند زبانه به نسبت خوب میباشد و برای هر زبان میتوانید از culture مورد نظر استفاده نمایید. در قسمت دوم این مطلب به بررسی بخش سمت سرور میپردازیم.
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="EntityFramework" version="4.3.1" />
</packages>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=4.3.1.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</configSections>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework">
<parameters>
<parameter value="Data Source=(localdb)\v11.0; Integrated Security=True; MultipleActiveResultSets=True" />
</parameters>
</defaultConnectionFactory>
</entityFramework>
</configuration>
namespace EF_Sample01.Models
{
public class Post
{
public int Id { set; get; }
public string Title { set; get; }
public string Content { set; get; }
public virtual Blog Blog { set; get; }
}
}
using System.Collections.Generic;
namespace EF_Sample01.Models
{
public class Blog
{
public int Id { set; get; }
public string Title { set; get; }
public string AuthorName { set; get; }
public IList<Post> Posts { set; get; }
}
}
using System.Data.Entity;
using EF_Sample01.Models;
namespace EF_Sample01
{
public class Context : DbContext
{
public DbSet<Blog> Blogs { set; get; }
public DbSet<Post> Posts { set; get; }
}
}
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
...
</configSections>
<connectionStrings>
<clear/>
<add name="Context"
connectionString="Data Source=(local);Initial Catalog=testdb2012;Integrated Security = true"
providerName="System.Data.SqlClient"
/>
</connectionStrings>
...
</configuration>
<connectionStrings>
<add name="MyContextName"
connectionString="Data Source=|DataDirectory|\Store.sdf"
providerName="System.Data.SqlServerCe.4.0" />
</connectionStrings>
public class Context : DbContext
{
public Context()
: base("ConnectionStringName")
{
}
using EF_Sample01.Models;
namespace EF_Sample01
{
class Program
{
static void Main(string[] args)
{
using (var db = new Context())
{
db.Blogs.Add(new Blog { AuthorName = "Vahid", Title = ".NET Tips" });
db.SaveChanges();
}
}
}
}
using EF_Sample01.Models;
namespace EF_Sample01
{
class Program
{
static void Main(string[] args)
{
//addBlog();
addPost();
}
private static void addPost()
{
using (var db = new Context())
{
var blog = db.Blogs.Find(1);
db.Posts.Add(new Post
{
Blog = blog,
Content = "data",
Title = "EF"
});
db.SaveChanges();
}
}
private static void addBlog()
{
using (var db = new Context())
{
db.Blogs.Add(new Blog { AuthorName = "Vahid", Title = ".NET Tips" });
db.SaveChanges();
}
}
}
}
exec sp_executesql N'insert [dbo].[Posts]([Title], [Content], [Blog_Id])
values (@0, @1, @2)
select [Id]
from [dbo].[Posts]
where @@ROWCOUNT > 0 and [Id] = scope_identity()',
N'@0 nvarchar(max) ,@1 nvarchar(max) ,@2 int',
@0=N'EF',
@1=N'data',
@2=1
using Microsoft.Data.Entity; using Microsoft.Data.Entity.Metadata; using System.Collections.Generic; using System.Linq; namespace UsingEF7WithSQLite { public class Blog { public int BlogId { get; set; } public string Url { get; set; } public List<Post> Posts { get; set; } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public int BlogId { get; set; } public Blog Blog { get; set; } } }
PM> Install-Package EntityFramework.SQLite –Pre
namespace UsingEF7SQLiteProvider { public class BloggingContext : DbContext { public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } protected override void OnConfiguring(DbContextOptions builder) { builder.UseSQLite(@"Data Source=.\BloggingDatabae.db"); } protected override void OnModelCreating(ModelBuilder builder) { builder.Entity<Blog>() .OneToMany(b => b.Posts, p => p.Blog) .ForeignKey(p => p.BlogId); // The EF7 SQLite provider currently doesn't support generated values // so setting the keys to be generated from developer code builder.Entity<Blog>() .Property(b => b.BlogId) .GenerateValueOnAdd(false); builder.Entity<Post>() .Property(b => b.BlogId) .GenerateValueOnAdd(false); } } }
Install-Package EntityFramework.Commands -Pre
Add-Migration MyFirstMigration Apply-Migration
using (var db = new BloggingContext()) { db.Database.AsMigrationsEnabled().ApplyMigrations(); }
using (var db = new Models.BloggingContext()) { db.Blogs.Add(new Models.Blog { Url = "https://www.dntips.ir" }); db.SaveChanges(); foreach (var item in db.Blogs) { Console.WriteLine(item.Url); } } Console.ReadLine();