Một Số Dòng Vẹt Dễ Nuôi Dành Cho Người Mới Bắt Đầu

Love-Birds

Hầu hết mọi người khi mới bắt đầu tiếp xúc hoặc mới tập chơi vẹt đều băn khoăn về việc lựa chọn loài nào sẽ là con cưng đầu lòng của mình. Về cơ bản dòng vẹt nào cũng đáng yêu cũng hay cũng đẹp và có thế mạnh và yếu riêng. Dưới đây Yêu Vẹt sẽ giới thiệu một số dòng vẹt phổ biến thích hợp cho người mới bắt đầu, mọi người cùng tham khảo :

1 .Yến Phụng – Vẹt Hồng Kông

yen phung
Ưu điểm :
– Dòng vẹt này có rất nhiều màu ,giá thành rẻ , sức khỏe tốt ít bênh, kích thước nhỏ nhắn nên khá nhanh, nói nhiều và tương đối tốt (điều kiện phải nuôidạy từ con non ) xinh xắn ngộ nghĩnh, tính thân thiện cao ,có thể dùng freeflight . Đặc biệt yến phụng rấy dễ bắt cặp sinh sản.
Nhược điểm :
– Kích thước khá gần như nhỏ nhất so với các loại khác ,IQ chỉ ở mức trung bình nhưng lại có đặc tính nên khá vất vả khi huấn luyện nhất là đối với các trò khó yêu cầu kỹ năng quan sát và lắng nghe.Tiếng kêu nhỏ và tương đối rủi ro khi freeflight.

2 . Cockatiels – Vẹt Mã Lai
vet ma lai
-Ưu điểm .
Lông mềm mượt nữ tính, có khả năng hót theo nhạc cũng như bắt trước giọng các loài chim khác rất tốt .Tính thân thiện cao, có thể dùng freeflight. giá thành khá rẻ, rất dễ bắt cặp sinh sản.
-Nhược điểm :
Do các thế hệ trước bị lỗi gen nên hầu hết Cockatiels có sức khỏe khá yếu , nói rất kém .IQ ở mức trung bình.

3 , Vẹt Ngực Hồng, (đầu xám đầu hồng )
vet nguc hong
Ưu điểm :
Vẹt Ngực Hồng có màu sắc sặc sỡ, khả năng nói ở mưc trung bình , giá thành rẻ , sức khỏe tốt ít bệnh, IQ ở mức trung bình.
– Nhược điểm :
Ơ giai đoạn chim mới lớn rất xấu ,Đặc tính bảo vệ lãnh thổ cao nên nếu thả chung lồng rất dễ cắn nhau .khá cộc tính , kỹ thuật bay rất kém nên ko phải dòng chim lý tưởng để thả bay freefligh.

4. Love bird
Love-Birds

– Ưu điểm :
Love bird rất xinh xắn dễ thương , nhiều màu sắc .đặc tính thân thiện , khá trung thành với chủ. có thể dùng cho freeflight .
– Nhược điểm :
Kích thước khá nhỏ , khả năng bắt trước giọng rất kém ,khá ồn.

5. Vẹt Má Vàng và Rinhneck :
vet ma vang

– Ưu điểm :
Vẹt Má Vàng và Rinhneck có lớp lông dạng sợi nhỏ nên rất mượt , đây là 1 dòng vẹt đuôi dài đẹp , có khả năng nói ở mức khá , giá tầm trung không quá đắt.
nhược điểm :
– Cục tính và tính lãnh thổ cao nên thường cắn nhau khi nhốt chung lồng . kỹ thuật bay kém rất ít được dùng trong freeflight.

6. Sun Conure
Sun Conure

– Ưu điểm :
Sun Conure có màu sắc sặc sỡ ,đặc tính rất trung thành và rất hài hước, kỹ thuật bay tốt là ứng của viên thả bay freeflight .IQ ở trung bình , giá thành rẻ so với các dòng vẹt nhập
– Nhược điểm :
Khá ồn, âm thanh chói tai , nói kém.

7. Vẹt Xám Châu Phi :
vet xam chau phi
– Ưu điểm :
Vẹt Xám Châu Phi nằm trong nhóm cao vẹt có IQ cao nhất , cùng với amazon đứng ở tốp số 1 về khả năng nói. Tuy kỹ thuật bay trung bình nhưng có thể dùng freeflight. Dòng vẹt nuôi lâu sẽ rất tình cảm và quấn chủ.
– Nhược điểm :
Vẹt Xám Châu Phi không có màu sặc sỡ như các dòng vẹt khác toàn thân hầu như toàn màu xám nên kén người thích. Lông nhiều bụi phấn .Tuy thả được bay freeflight nhưng do tập tính độc lập nên việc dạy bay tương đối khó khăn. Giá thành tương đối cao từ 700-1000 USD.

8. Cokatoo (Blue Eyed Cokatoo, Sunfu Cockatoo, Moluca Ccckatoo … )
blue eyed Sulphur-crested Cockatoo
– Ưu điểm
Dòng Cockatoo được cho là dòng vẹt thân thiện nhất, mức độ thông minh chỉ sau những dòng vẹt như xám .Cánh và đuôi to nên chúng là những ứng cử viên hàng đầu trong việc thả bay freeflight, Chúng có chiếc mào rất đẹp và nữ tính , khả năng nói tốt , thích môi trường sôi động và đặc biệt thích nhảy theo nhạc .
– Nhược điểm :
Cockatoo rất ồn , Lớp lông có bụi phấn nhiều nên không thích hợp cho trẻ chơi, toàn thân bao phủ lớp lông trắng khá dễ dính bẩn. Giá thành ở mức cao ( từ 1000 – 1700 USD )
Các bạn có thể tham khảo thêm thông tin về dòng vẹt này tai link dưới :
http://yeuvet.com/cac-dong-vet/vet-chau-uc/

9. Blue and Gold Macaw
macaw_bluethroated4 Blue and Gold Macaw
– Ưu điểm :
Blue and Gold Macaw có kích cỡ to , dài . màu sắc sặc sỡ .đặc tính thân thiện. cũng là ứng cử viên sáng giá cho thả freeflight , khả năng nói tốt và thông minh.
– Nhược điểm :
Do đặc điểm của tuyến mồ hôi nên Blue and Gold có mùi khá hôi , đặc tính rất ồn, giọng hét to , do kích thước lớn và nặng nên khá chậm chạp , hơi bất tiện khi để đứng trên tay do nặng, giá thành cao ( từ 1600-2000 USD)
Xem chi tiết về Blue and Gold tại link dưới :
http://yeuvet.com/blue-and-gold-macaws/

10. Electus
Eclectus

– Ưu điểm :
Electus có màu sắc sặc sỡ , phân biệt ngay trống mái từ non và trống mái mỗi con 1 màu đẹp 1 vẻ , đặc tính hiền lành , khả năng nói rất tốt , kỹ thuật bay tốt có thể dùng thả freeflight. ít ồn ào.
– Nhược điểm :
Đặc tính lầm lì gây khó khăn trong đào tạo, tiếng hét khá đáng sợ. giá thành ở mức trung bình ( từ 600-800 USD)

11. Caique
caique

– Ưu điểm
Caique sở hữu bộ lông khá ngộ nghĩnh , cùng với sự ngộ nghĩnh của bộ lông thì tính cách cũng ngộ nghĩnh và hài hước . là 1 dòng vẹt được đánh giá rất mạnh về sự đáng yêu của chúng . IQ khá, không ồn ào.
– Nhược điểm :

Update …………….

Bài viết chỉnh sửa dựa trên nội dung của #Lê_Bá_Chủ/Yêu Vẹt Club

14 điều cần nhớ để Cockatiel luôn cảm thấy hạnh phúc

Bỗng dưng một ngày không xác định, chú vẹt Cockatiel nhà bạn trở nên u uất, cắn phá xung quanh vô tội vạ. Vâng, nếu trường hợp chẳng may đó xảy ra, bạn nên đọc bài viết này, đề cập đến việc giữ cho vẹt cưng luôn cảm thấy thoải mái và vui vẻ.

 

1. Dinh dưỡng

670px-Keep-Your-Cockatiel-Happy-Step-1

Trong quá trình nuôi Cockatiel, bạn cần cung cấp khẩu phần ăn đầy đủ dinh dưỡng. Các bé Cockatiels yêu vô cùng lòng trắng trứng, ngô, nho, hạt hướng dương, và hỗn hợp hạt. Chẳng vất vả gì khi bạn có thể dễ dàng tậu các loại thức ăn sẵn tại các cửa hàng thú cưng. Tuy nhiên có sự khác biệt về công thức trong các loại thức ăn viên viên khác nhau, những công thức này được thiết kế bởi bác sĩ thú y nhằm cung cấp một chế độ ăn uống đa dạng và cân bằng dinh dưỡng. Bạn nên thay đổi nhiều loại thức ăn viên và bổ sung thêm rau quả để giữ cho lượng tiêu thụ hạt giống ở mức tối thiểu (nghĩa là trong khẩu phần ăn của Cockatiel, hạt giống chỉ chiếm từ 33% trở xuống). Với một chế độ ăn uống buồn tẻ chú yếu là hạt giống (không rau củ, không thức ăn viên hoặc hai loại ấy chiếm tỉ lệ nhỏ), đặc biệt là hạt giàu chất béo, hậu quả là tuổi thọ của Cockatiel bị rút ngắn đáng kể.

 

2. Lồng đủ tiêu chuẩn.

670px-Keep-Your-Cockatiel-Happy-Step-2

Hãy chắc chắn rằng Cockatiel nhà bạn tung tăng bay nhảy trong chiếc lồng phù hợp với kích thước của chú. Không cần quá lớn – trung bình vừa là tốt. Với một chiếc lồng bé tẹo, vẹt cưng sẽ cần chui ra ngoài nhiều hơn để hoàn thành những bài tập thể dục cần thiết và giải trí tinh thần.

 

3. Trò chơi niềm tin

670px-Keep-Your-Cockatiel-Happy-Step-3

Vẹt cưng Cockatiel không biết chơi trò bước lên tay (đây là pha đơn giản nhất trong số các hành động tương tác giữa người và vẹt) vì thế hãy cố gắng hướng dẫn chú. Một cách chậm rãi bạn bắt đầu bằng cách nói chuyện điềm tĩnh với Cockatiel khi chú vẫn đang trong lồng, mục đích để làm quen. Một khi bé vẹt đã dần quen với sự có mặt của bạn, bạn chính thức có thể bắt đầu bài tập. Điều bạn cần là kiên nhẫn, cứ để cho vẹt cưng càm thấy thoải mái khi ở bên bạn. Lâu dần thói quen ấy hình thành nên sợi dây liên kết bền vững như kiểu nam châm trái cực hút bạn và chú Cockatiel đến gần nhau. Một khi vẹt cưng cảm thấy hoàn toàn tin tưởng và sẵn sàng bước khẽ lên bàn tay của bạn, bạn có thể từ từ mang em ấy ra khỏi lồng, NHƯNG nhớ là chậm thôi, kẻo ăn đạn đấy!

 

4. Sở thích của Cocky: được vuốt ve đầu và cổ.

670px-Keep-Your-Cockatiel-Happy-Step-4

Các chuyên gia đã phát hiện ra rằng gần như tất cả loài vẹt Cockatiel bị kích thích khi được chủ nuôi gãi đầu và cổ, điều lạ ở đây là chúng thích được vuốt ve như thế cho đến khi bị trầy xước nhẹ. Không chỉ riêng một số chú Cockatiel được chủ âu yếm, ôm ấp cả ngày mà đến cả những em vẹt làm kiểng, chỉ líu lo trong lồng cũng thích được gãi ngứa phần đầu và cổ ^^. Dấu hiệu dễ dàng bắt gặp là vẹt cưng chúi đầu xuống và hướng về phía bạn.

 

5. Phiền nhiễu

670px-Keep-Your-Cockatiel-Happy-Step-5

Đừng bao giờ lạm dụng quá mức việc tiếp xúc. Thời gian là vàng bạc. Tuy là vẹt cưng đã thân thiết với bạn, nhưng cố gắng không làm phiền chú quá mức. Một vài lần trong một ngày là một khởi đầu tốt. Đến khi vẹt cưng đã dần quen với sự có mặt của bạn, hãy sẵn sàng tham gia hoạt động cùng chú. Nếu Cockatiel của bạn đi đi lại lại gần cửa lồng thể hiện ý muốn ra ngoài. Đôi khi ra ngoài là để đùa nghịch hoặc đơn giản chì là muốn gần bạn hơn hoặc muốn được ôm ấp và vuốt ve đầu. Hãy dành thời gian để tìm hiểu những gì Cockatiel nhà bạn thích, khi đó bạn sẽ hiểu người bạn lông vũ và chung sống vui vẻ trong nhiều năm sắp tới. Đừng quên rằng vẹt là những sinh vật được tạo hóa trong tự nhiên hoang dã và có tính bầy đàn, vì thế chúng cần có bạn đồng hành. Bạn biết đấy, tình yêu thương bạn dành cho vẹt cưng sẽ âm thầm kéo dài tuổi thọ của chú.

 

6. Lợi ích của nhánh cây.

670px-Keep-Your-Cockatiel-Happy-Step-6

Việc tiếp theo cần làm là bố trí nhiều nhánh cây trong lồng để Cockatiel bước ngang bước dọc, nhảy tung tăng và quẩy thoải mái xung quanh lồng. Chồng chéo một cách hợp lý, vẹt cưng có thể bay lượn xuyên qua các nhánh cây. Chớ bắt chước trong phim nâng cấp độ khó lên level max nhằm buộc vẹt cưng phải bay dốc thẳng đứng để vượt qua chứng ngại vật (phim ảnh chỉ mang tính chất giải trí =.=). Nên nhớ chú vẹt nhà bạn là một chú Cocky đã được thuần hóa, do đó việc bay thẳng như những chiếc phi cơ là điều không tưởng. Tùy thuộc vào độ sáng tạo và tinh tế của bạn mà các nhánh cây được tạo hình một cách độc đáo nhằm thu hút em vẹt, tạo cho Cocky cảm giác thích thú như sống trong rừng cây hoang dã. Vật liệu làm nên các nhánh cây cũng cần được chú ý bởi nếu làm từ các cây que phết sơn thường gây cảm giác nhàm chán và khó chịu cho các loài chim (chỉ nhìn thôi cũng đã thấy giả tạo chán). Lý tưởng nhất là làm bằng gỗ tự nhiên. Cũng phải thôi, chân thực quá mà lại!!!

 

7. Lá cây, búp bê, đồ hàng ….

670px-Keep-Your-Cockatiel-Happy-Step-7

Việc đặt đủ các loại đồ chơi lồng khá là quan trọng. Sao lại thế? Vì Cockatiels, tính cách cũng tựa hầu hết những loài vẹt cưng khác, chúng thích phá phách mọi thứ. Chỉ nên đặt lá cây (hái trong vườn) và những đồ chơi nho nhỏ đủ để Cocky nghịch nhưng không mất sức. Cẩn thận với việc hái lá cây nhé! Không phải tất cả cây và bụi cây nào cũng đều an toàn đối với vẹt Cockatiels.

 

8. Chọn bài tập thể dục nào đây?

670px-Keep-Your-Cockatiel-Happy-Step-8

Hãy khuyến khích vẹt cưng tập thể dục. Cockatiels cần tập luyện thường xuyên trong môi trường an toàn. Trước tiên là tắt quạt, đóng cửa ra vào và cửa sổ, đưa vẹt cưng ra khỏi lồng hoặc cứ để tự chú chui ra và đi bộ xung quanh một cách tự do.

Với sức khỏe tràn trề, Cocky có thể bay lượn vèo vèo ngay cả khi đã tỉa bớt cánh. Do đó, ngại gì mà không thách thức chú với cái chướng ngại vật để chú khoe khả năng tung cánh mà chỉ có loài chim mới có (trừ dơi thuộc họ chim chuột). Đối với loài chim, không có bài tập thể dục nào tốt hơn ngoài việc vỗ cánh bay. Bạn nên ghi chú lại quá trình bay của vẹt cưng xem có cải thiện từng ngày hay không (quãng đường bay, thời gian bay, thời gian nghỉ và dừng lại, độ cao của vật chắn…).

Nếu bạn liều lĩnh, để cửa ra vào và cửa sổ mở, vẫn có khả năng quay về nhưng tỷ lệ ấy không cao, bởi vì sao, chúng thường bị hấp dẫn bởi những điều mới lạ, không gian thoáng đãng của bầu trời và cả lũ chim sẻ, bồ cầu bay từng đàn. Một chú Cocky khỏe khoắn về mặt thể chất và tinh thần, được đối đãi yêu thương hết mực, chẳng nghi ngờ gì về việc chú sẽ vòng lại về nhà và sà vào lòng bạn.

 

9. Bài học giản đơn.

670px-Keep-Your-Cockatiel-Happy-Step-9

Cockatiels là một trong những loài sinh vật đáng yêu và thông minh, đôi khi sự kích thích về tinh thần (nôm na là hại não ^^) là rất cần thiết. Bài học đơn giản nhất bạn có thể dễ dàng dạy cho chú, thậm chí bài này còn dễ hơn cả bài học “đậu lên tay” nữa cơ, đó là “nhắm mục tiêu”.

Cầm cây gậy trong tay, bắt đầu thật chậm rãi, đưa gậy xích lại gần Cocky. Bạn đang thu hút sự chú ý của chú cho đến khi sự tò mò được kích thích, mắt dõi theo, tinh thần hưng phấn và chú tự động chạm mỏ vào cây gậy, thế là thành công. Đừng quên ngay sau đấy tung bông tung hoa khen thưởng hết lời nhé! Dần dần, khi vẹt cưng bắt đầu hứng thú với bài học, hãy đặt thanh gỗ xa hơn và xa hơn nữa khiến Cocky phải tiến tới để chạm vào nó.

 

10. Nói không với bạo lực.

670px-Keep-Your-Cockatiel-Happy-Step-10

Tuyệt đối không bao giờ la mắng hoặc đánh đòn vẹt cưng. Khi huấn luyện một chú vẹt, bạn chỉ nên tập trung vào việc cải thiện hiệu quả một cách tích cực từ lần này sang lần khác.

Nếu vẹt cưng không hiểu ý, hờ hững, đối xử tệ bạc với bạn, chỉ đơn giản là cố gắng quên ngay đi, và thay vì chờ đợi cho đến khi Cocky thực hiện điều gì đó đúng theo ý bạn, đừng hẹp hòi mà cứ thưởng cho chú vì bất kì hành vi nào (trừ hành vi tiêu cực).

Nếu chú cào cấu hoặc quắp bạn, hoặc chiếm đoạt một cái gì đó nó không nên, một cách bình tĩnh bạn nói “không” và nghiêm túc nhìn thẳng vào mắt chú.

 

11. Cách thức vệ sinh cá nhân.

670px-Keep-Your-Cockatiel-Happy-Step-11

Nhờ vào những cuộc cách mạng khoa học kỹ thuật, việc tắm chim cũng vì thế mà trở nên đa dạng. Một số loài chim thích cuộn quanh trong bồn tắm, số khác thích tận hưởng sự phun sương từ một bình xịt và số còn lại thích khỏa thân tắm dưới vòi sen cùng chủ nuôi.

Áp dụng hết để xem Cocky nhà bạn khoái chí nhất với phương pháp nào. Được biết Cockatiels là loài chim sống trên sa mạc và chúng không cần tắm quá nhiều, một lần một tuần, dư thừa chả có ích gì chỉ ngoài lãng phí và đôi khi chẳng may còn gây hại đến sức khỏe vẹt cưng. Thêm nữa, nhiệt độ nước tắm cũng phải được điều chỉnh, từ mát đến ấm dần (không để nóng hoặc lạnh), và điều cấm kỵ là đặt Cocky trực tiếp dưới vòi sen. Dùng tay cản dòng nước từ vòi sen để chúng bắn tung tóe vào vẹt cưng (giảm áp lực nước trước khi chạm vào người Cocky). Dùng máy phun sương đương nhiên cực kì nhẹ nhàng và quyến rũ hơn vòi sen.

 

12. Trọng lượng.

670px-Keep-Your-Cockatiel-Happy-Step-12

Đơn giản là dùng cân và cân chim. Nói thì dễ, làm vô cùng khó nếu chú vẹt nhà bạn rơi vào tình trạng không khỏe, chú sẽ làm đủ mọi cách để che giấu sự yếu đuối. Giảm kí đột ngột thường có thể là dấu hiệu đầu tiên để chẩn đoán bệnh. Nếu bạn thấy bất cứ điều gì đó không ổn, vẹt cưng khó chịu, xoắn loạn xạ ngay khi kiểm tra sức khỏe, liên hệ với bác sĩ thú y (tốt hơn là bác sĩ thú y chuyên ngành về gia cầm) ngay lập tức.

 

13. Chất thải.

670px-Keep-Your-Cockatiel-Happy-Step-13

Để ý đến phân thải ra của em vẹt. Sư thay đổi về màu sắc (trắng hoặc xanh hoặc vàng), tính chất (vón cục hoặc lỏng), hoặc tần số (số lần đi ngoài), đó là những dấu hiệu bạn cần nêu ra để tham khảo ý kiến của bác sĩ thú y.

 

14. Giấc ngủ ngon.

670px-Keep-Your-Cockatiel-Happy-Step-14

Giấc ngủ bao giờ cũng quan trọng, cơ thể cần một khoảng thời gian để phục hồi sau một ngày dài vận động. Trung bình Cockatiels cần 10-12 giờ để ngủ. Tạo không gian yên tĩnh và tương đối tối để vẹt cưng chìm vào giấc ngủ ngon lành mỗi đêm. Trường hợp điều kiện không cho phép (nhà gần chợ, trường học hay nhà mặt phố…), việc đảm bảo yên tĩnh trong ít nhất 10-12 tiếng mỗi đêm không thể thực hiện được, đừng quá lo lắng. Giấc ngủ có thể được bổ sung vào bất kỳ thời gian trong ngày (ngủ trưa) miễn sao bạn tạo được không gian yên ắng và chập choạng tối để Cocky cảm thấy an tâm và say nồng vào giấc ngủ.

 

Khuyên nhủ:

Nếu bạn biết cách chăm sóc Cockatiel và thật sự hiểu người bạn lông vũ của mình, chú có thể sống từ 20-30 năm. Trước khi tậu về, hãy chắc chắn rằng giữa cuộc sống bộn bề bạn vẫn có thể nuôi dạy và chăm sóc vẹt cưng thật tốt.

Sở thích ăn uống của Cocky là trái cây sấy khô. Mua thực phẩm từ các cửa hàng vật nuôi hoặc siêu thị, nhớ là chú ý nhà sản xuất (tin cậy) và hạn sử dụng nhé!

 

Thận trọng:

Khói hiện diện trong không khí ngay lập tức ảnh hưởng không tốt đến vẹt cưng nhà bạn. Chất Teflon sinh ra từ chảo quá nóng là hiểm họa nghiêm trọng đối với loài chim. Ngoài ra, chất tẩy rửa mạnh, khói thuốc lá, cũng như mùi nồng nặc của các loại nước hoa … tất cả đều có thể gây tổn hại cho phổi của loài cầm.

Không cho vẹt cưng nếm thử chocolate, bơ, hành tây vì chúng đều chứa các hợp chất có thể gây bệnh và thậm chí nếu nặng sẽ dẫn đến tử vong.

Một đặc điểm đáng chú ý của loài vẹt Cockatiel là thường dễ bị “sợ bóng đêm”. Bạn sẽ thấy nỗi sợ ấy khi chúng thức dậy đột ngột trong tình trạng hoảng loạn, không phải lúc nào cũng thế, chỉ là đôi khi do có tiếng động lớn (nhà hàng xóm hứng chí karaoke) hoặc môi trường thay đổi đột ngột (dịch chuyển lồng). Điều này được giải thích là trong tự nhiên, Cocky thường hoạt động trong môi trường mở, chúng phản ứng mạnh với những nguy cơ gặp phải.

Nếu phải đối phó với hiểm nguy trong lồng, hành vi này có thể tự chuốc lấy nguy hiểm hoặc thậm chí gây tử vong. Bước đầu tiên cần làm là giảm thiểu những gì đột ngột xảy ra cho Cocky. Tạo dựng không gian yên tĩnh vào ban đêm và giữ lại một ánh đèn ngủ le lói để chú vẫn có thể nhìn thấy chứ không phải hoàn toàn quáng gà mỗi khi chú hoảng sợ. Bước thứ hai là chuẩn bị sẵn sàng trong những trường hợp tai nạn xấu có thể xảy đến. Như trong lúc quá sợ hãi, vẹt cưng vô tình làm đứt sợi lông máu (là một chiếc lông đang phát triển nhờ vào mạch máu chạy bên dưới mang dinh dưỡng đến nuôi nó), em vẹt có thể chảy máu đến chết nếu không được kịp thời phát hiện. Xử lý ngay nơi rò rỉ (bông băng y tế quấn lại, bột mì, bột bắp rắc vào) nhằm ngăn chặn sự chảy máu vào thời điểm đó cho đến khi bạn đưa vẹt cưng đến bác sĩ thú y.

 

Chúc bạn thành công!

 

 

Nguồn: wikiHow

Decouple OWIN Authorization Server from Resource Server

Decouple OWIN Authorization Server from Resource Server

Recently I’ve received lot of comments and emails asking how we can decouple the OWIN Authorization Server we’ve built in the previous posts from the resources we are protecting. If you are following the posts mentioned below you will notice that we’ve only one software component (API) which plays both roles: Authorization Server and Resource Server.

There is nothing wrong with the the current implementation because OAuth 2.0 specifications never forces the separation between the Authorization Server and the Resource Server, but in many cases and especially if you have huge resources and huge number of endpoints you want to secure; then it is better from architecture stand point to decouple the servers. So you will end up having a stand alone Authorization Server, and another stand alone Resource Server which contains all protected resources, as well this Resource Server will understand only the access tokens issued from the Authorization Server.

You can check the source code on Github.


OAuth 2.0 Roles: Authorization Server, Resource Server, Client, and Resource Owner

Before we jump into implementation and how we can decouple the servers I would like to emphasis on OAuth 2.0 roles defined in the specifications; so we’ll have better understanding of the responsibility of each role, by looking at the image below you will notice that there are 4 roles involved:

OAuth 2.0 Roles

Resource Owner:

The entity or person (user) that owns the protected Resource.

Resource Server:

The server which hosts the protected resources, this server should be able to accept the access tokens issued by the Authorization Server and respond with the protected resource if the the access token is valid.

Client Applications:

The application or the (software) requesting access to the protected resources on the Resource Server, this client (on some OAuth flows) can request access token on behalf of the Resource Owner.

Authorization Server:

The server which is responsible for managing authorizations and issuing access tokens to the clients after validating the Resource Owner identity.

Note: In our architecture we can have single Authorization Server which is responsible to issue access token which can be consumed by multiple Resource Servers.

Decoupling OAuth 2.0 OWIN Authorization Server from Resource Server

In the previous posts we’ve built the Authorization Server and the Resource Server using a single software component (API), this API can be accessed on (http://ngauthenticationAPI.azurewebsites.net), in this demo I will guide you on how to create new standalone Resource API and host it on different machine other than the Authorization Server machine, then configure the Resource API to accept only access tokens issued from our Authorization Server. This new Resource Server is hosted on Azure can be accessed on (http://ngauthenticationResourcesAPI.azurewebsites.net).

Note: As you noticed from the previous posts we’ve built a protected resource (OrdersController) which can be accessed by issuing HTTP GET to the end point(http://ngauthenticationAPI.azurewebsites.net/api/orders), this end point is considered as a resource and should be moved to the Resource Server we’ll build now, but for the sake of keeping all the posts on this series functioning correctly; I’ll keep this end point in our Authorization Server, so please consider the API we’ve built previously as our standalone Authorization Server even it has a protected resource in it.

Steps to build the Resource Server

In the steps below, we’ll build another Web API which will contain our protected resources, for the sake of keeping it simple, I’ll add only one secured end point named “ProtectedController”, any authorized request to this end point should contain valid bearer token issued from our Authorization Server, so let’s start implementing this:

Step 1: Creating the Resource Web API Project

We need to add new ASP.NET Web application named “AngularJSAuthentication.ResourceServer” to our existing solution named “AngularJSAuthentication”, the selected template for the new project will be “Empty” template with no core dependencies, check the image below:

Resourse Server New Project

Step 2: Install the needed NuGet Packages

This project is empty so we need to install the NuGet packages needed to setup our OWIN Resource Server, configure ASP.NET Web API to be hosted within an OWIN server, and configure it to only uses OAuth 2.0 bearer tokens as authorization middle ware; so open NuGet Package Manager Console and install the below packages:

The usage for each package has been covered on the previews posts, feel free to check this post to know the rational of using each package is used for.

Important note: In the initial post I was using package “Microsoft.Owin.Security.OAuth” version “3.0.0” which differs from the version used in the Authorization Server version “2.1.0”, there was a bug in my solution when I was using 2 different versions, basically the bug happens when we send an expired token to the Resource Server, the result for this that Resource Server accepts this token even if it is expired. I’ve noticed that the properties for the Authentication ticket are null and there is no expiry date. To fix this issue we need to unify the assembly version “Microsoft.Owin.Security.OAuth” between the Authorization Server and the Resource Server, I believe there is breaking changes between those 2 versions that’s why ticket properties are not de-crypted and de-serialized correctly.  Thanks for Ashish Verma for notifying me about this.

Step 3: Add OWIN “Startup” Class

Now we want to add new class named “Startup”. It will contain the code below:

As we’ve covered in previous posts, this class will be fired once our Resource Server starts, as you noticed I’ve enabled CORS so this resource server can accept XHR requests coming from any origin.

What worth noting here is the implementation for method “ConfigureOAuth”, inside this method we are configuring the Resource Server to accept (consume only) tokens with bearer scheme, if you forget this one; then your Resource Server won’t understand the bearer tokens sent to it.

Step 3: Add “WebApiConfig” Class

As usual we need to add the WebApiConfig class under folder “App_Start” which contains the code below:

This class is responsible to enable routing attributes on our controller.

Step 4: Add new protected (secured) controller

Now we want to add a controller which will serve as our protected resource, this controller will return list of claims for the authorized user, those claims for sure are encoded within the access token we’ve obtained from the Authorization Server. So add new controller named “ProtectedController” under “Controllers” folder and paste the code below:

Notice how we attribute the controller with [Authorize] attribute which will protect this resource and only will allow HTTP GET requests containing a valid bearer access token sent in the authorization header to pass, in more details when the request is received the OAuth bearer authentication middle ware will perform the following:

  1. Extracting the access token which is sent in the “Authorization header” with the “Bearer” scheme.
  2. Extracting the authentication ticket from access token, this ticket will contain claims identity and any additional authentication properties.
  3. Checking the validity period of the authentication ticket.

Step 5: Hosting the Authorization Server and Resource Server on different machines

Until this step and if you are developing locally on your dev machine, if you tried to obtain an access token from your Authorization Server i.e. (http://AuthServer/token) and then send this access token to the secured end point in our Resource Server i.e. (http://ResServer/api/protected) the result for this request will pass and you will have access to the protected resource, how is this happening?

One your dev machine once you request an access token from your Authorization Server; the OAuth middleware will use the default data protection provider in your Authorization Server, so it will use the “validationKey” value in machineKey node stored in machine.config file to issue the access token and protect it. The same case applies when you send the access token to your Resource Server, it will use the same machineKey to decrypt the access token and extract the authentication ticket from it.

The same applies if you are planing on your production environment to host your Authorization Server and your Resource Server on the same machine.

But in our case I’m hosting my Authorization Server on Azure website using West-US region, and my Resource Server is hosted on Azure websites on East-US region so both are totally on different machines and they share different machine.config files. For sure I can’t change anything on machine.config file because it is used by different sites on the Azure VMs I’m hosting my APIs on it.

So to solve this case we need to override the machineKey node for both APIs (Authorization Server and Resource Server) and share the same machineKey between both web.config files.

To do so we need to generate a new machineKey using this online tool, the result after using the tool will be as the below: As advised by Barry Dorrans it is not recommend to use an online tool to generate your machineKey because you do not know if this online tool will store the value for your machineKey in some secret database, it is better to generate the machineKey by your self.

To do so I’ll follow the steps mentioned in article KB 2915218, Appendix A which uses Power Shell commands to generate machineKey, so after you open Power Shell and run the right command as the image below you will receive new machineKey
PowerShell MachineKey

Note: Do not use those for production and generate a new machineKey for your Servers using Power Shell.

After you generate it open the web.config file for both the Authorization Server (AngularJSAuthentication.API project) and for the Resource Server (AngularJSAuthentication.ResourceServer) and paste the machineKey node inside the <system.web> node as the below:

By doing this we’ve unified the machineKey for both Authorization Server and Resource Server (Only this API in case of Azure shared hosting or any other shared host) and we are ready now to test our implementation.

Step 6: Testing the Authorization Server and Resource Server

Now we need to obtain an access token from our Authorization Server as we did before in the previous posts, so we can issue HTTP POST to the end point (http://ngauthenticationapi.azurewebsites.net/token) including the resource owner username and password as the image below:

Obtain Access Token

If the request succeeded we’ll receive an access token, then we’ll use this access token to send HTTP GET request to our new Resource Server using the protected end point (http://ngauthenticationresourcesapi.azurewebsites.net/api/protected), for sure we need to send the access token in the Authorization header using bearer scheme, the request will be as the image below:

Consume Access Token

As you notice we’re now able to access the protected resource in our new Resource Server and the claims for this identity which obtained from the Authorization Server are extracted.

That’s it for now folks, hopefully this short walk through helped in understanding how we can decouple the Authorization Server from the Resource Server.

If you have any comment or question please drop me a comment

You can use the following link to check the demo application.

You can use the following link to test the Authorization Server (http://ngauthenticationAPI.azurewebsites.net),

You can use the following link to test the Resource Server (http://ngauthenticationResourcesAPI.azurewebsites.net) 

You can check the source code on Github.

Follow me on Twitter @tjoudeh

References

ASP.NET MVC 5 Internationalization

Introduction

If your website targets users from different parts of the world, these users might like to see your website content in their own language. Creating a multilingual website is not an easy task, but it will certainly allow your site to reach more audience. Fortunately, the .NET Framework already has components that support different languages and cultures.

We will build an ASP.NET MVC 5 web application that contains the following features:

  • It can display contents in different languages.
  • It autodetects the language from the user’s browser.
  • It allows the user to override the language of their browser.

Globalization and Localization in ASP.NET

Internationalization involves Globalization and Localization. Globalization is the process of designing applications that support different cultures. Localization is the process of customizing an application for a given culture.

The format for the culture name is “<languagecode2>-<country/regioncode2>”, where <languagecode2> is the language code and <country/regioncode2> is the subculture code. Examples include es-CL for Spanish (Chile) and en-US for English (United States).

Anyway, Internationalization is often abbreviated to “I18N”. The abbreviation takes the first and last letters and the number of letters between them, so 18 stands for the number of letters between the first “I” and the last “N”. The same applies to Globalization (G11N), and Localization (L10N).

ASP.NET keeps track of two culture values, the Culture and UICulture. The culture value determines the results of culture-dependent functions, such as the date, number, and currency formatting. The UICulture determines which resources are to be loaded for the page by the ResourceManager. The ResourceManager simply looks up culture-specific resources that is determined by CurrentUICulture. Every thread in .NET has CurrentCulture and CurrentUICulture objects. So ASP.NET inspects these values when rendering culture-dependent functions. For example, if current thread’s culture (CurrentCulture) is set to “en-US” (English, United States), DateTime.Now.ToLongDateString() shows “Saturday, January 08, 2011”, but if CurrentCulture is set to “es-CL” (Spanish, Chile) the result will be “sábado, 08 de enero de 2011”.

Now, let’s review the terms used so far:

  • Globalization (G11N): The process of making an application support different languages and regions.
  • Localization (L10N): The process of customizing an application for a given language and region.
  • Internationalization (I18N): Describes both globalization and localization.
  • Culture: It is a language and, optionally, a region.
  • Locale: A locale is the same as a culture.
  • Neutral culture: A culture that has a specified language, but not a region. (e.g. “en”, “es”)
  • Specific culture: A culture that has a specified language and region. (e.g. “en-US”, “en-GB”, “es-CL”)

Why do we need a region? Isn’t a language alone enough?

You might not need a region at all. It is true that English in the United States is not the same as English in the United Kingdom but if your application just shows English text readable to people from these English-speaking countries, you will not need a region. The problem arises when you need to deal with numbers, dates, and currencies. For example, compare the following output for two different Spanish-speaking regions (Chile, Mexico):

int value = 5600;

Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("es-CL");
Console.WriteLine(DateTime.Now.ToShortDateString());
Console.WriteLine(value.ToString("c"));

Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("es-MX");
Console.WriteLine(DateTime.Now.ToShortDateString());
Console.WriteLine(value.ToString("c"));

// Output
26-07-2011 // Date in es-CL, Spanish (Chile)
$5.600,00 // Currency in es-CL, Spanish (Chile)

26/07/2011 // Date in es-MX, Spanish (Mexico)
$5,600.00 // Currency in es-MX, Spanish (Mexico)
  

You can notice the difference in date and currency format. The decimal separator in each region is different and can confuse people in the other region. If a Mexican user types one thousand in their culture “1,000”, it will be interpreted as 1 (one) in a Chilean culture website. We mainly need regions for this type of reasons and not much for the language itself.

How to Support Different Languages in ASP.NET MVC

There are two ways to incorporate different languages and cultures in ASP.NET MVC:

 

  1. By using resource strings in all our site views.
  2. By using different set of views for every language and locale.
  3. By mixing between 1 and 2

 

Which one is the best?
It is a matter of convenience. Some people prefer to use a single view for all languages because it is more maintainable. While others think replacing views content with code like “@Resources.Something” might clutter the views and will become unreadable. Some project requirements force developers to implement different views per language. But sometimes you have no choice where layout has to be different like right-to-left languages. Even if you set dir=”rtl”, this may not be enough in real applications unless the project’s UI layout is really simple. Perhaps, a mix of the two is the best. Anyway, for this example, it makes sense to use resources since we won’t have any issue with the layout for the Spanish, English, and Arabic languages that we will use.

How can ASP.NET guess the user’s language?

On each HTTP request, there is a header field called Accept-Language which determines which languages the user’s browser supports:

        Accept-Language: en-us,en;q=0.5

This means that my browser prefers English (United States), but it can accept other types of English. The “q” parameter indicates an estimate of the user’s preference for that language. You can control the list of languages using your web browser.


Internet Explorer


Firefox

Globalizing our Web Site

We will create a new ASP.NET MVC web application and globalize it step by step.

Click “File->New Project” menu command within Visual Studio to create a new ASP.NET MVC 5 Project. We’ll create a new project using the “MVC” template.

Creating the Model

We’ll need a model to create our web application. Add a class named “Person” to the “Models” folder:

 

public class Person
{
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
        public string Email { get; set; }
        public string Biography { get; set; }
}
        

 

Internationalizing Validation Messages

Our model presented above contains no validation logic, and this is not the case in normal applications nowadays. We can use data annotation attributes to add some validation logic to our model. However, in order to globalize validation messages, we need to specify a few extra parameters. The “ErrorMessageResourceType” indicates the type of resource to look up the error message. “ErrorMessageResourceName” indicates the resource name to lookup the error message. Resource manager will pick the correct resource file based on the current culture.

Now modify the “Person” class and add the following attributes:

 

    public class Person
    {
        [Display(Name = "FirstName", ResourceType = typeof(Resources.Resources))]    
        [Required(ErrorMessageResourceType = typeof(Resources.Resources),
                  ErrorMessageResourceName = "FirstNameRequired")]
        [StringLength(50, ErrorMessageResourceType = typeof(Resources.Resources),
                          ErrorMessageResourceName = "FirstNameLong")]
        public string FirstName { get; set; }
        [Display(Name = "LastName", ResourceType = typeof(Resources.Resources))]    
        [Required(ErrorMessageResourceType = typeof(Resources.Resources),
                  ErrorMessageResourceName = "LastNameRequired")]
        [StringLength(50, ErrorMessageResourceType = typeof(Resources.Resources),
                          ErrorMessageResourceName = "LastNameLong")]
        public string LastName { get; set; }
        [Display(Name = "Age", ResourceType = typeof(Resources.Resources))]    
        [Required(ErrorMessageResourceType = typeof(Resources.Resources),
                  ErrorMessageResourceName = "AgeRequired")]
        [Range(0, 130, ErrorMessageResourceType = typeof(Resources.Resources),
                       ErrorMessageResourceName = "AgeRange")]
        public int Age { get; set; }
        [Display(Name = "Email", ResourceType = typeof(Resources.Resources))]    
        [Required(ErrorMessageResourceType = typeof(Resources.Resources),
                  ErrorMessageResourceName = "EmailRequired")]
        [RegularExpression(".+@.+\\..+", ErrorMessageResourceType = typeof(Resources.Resources),
                                         ErrorMessageResourceName = "EmailInvalid")]
        public string Email { get; set; }
        [Display(Name = "Biography", ResourceType = typeof(Resources.Resources))]    
        public string Biography { get; set; }
    }
     

 

Localizing Data Annotations Validation Messages

Because we need to perform data validation on our model using Data Annotations, we will have to add translated resource strings for every culture our site will support. In this case, English, Spanish, and Arabic.

We will store resource files in a separate assembly, so we can reference them in other project types in the future.

Right click on the Solution and then choose the “Add->New Project” context menu command. Choose “Class Library” project type and name it “Resources”.

Now right click on “Resources” project and then choose “Add->New Item” context menu command. Choose “Resource File” and name it “Resources.resx”. This will be our default culture (en-US) since it has no special endings. Add the following names and values to the file like below:

Remember to mark the resource’s access modifier property to “public”, so it will be accessible from other projects.

Now create a new resource file and name it “Resources.es.resx” and add the following names and values like below:

Now, do the same for the Arabic version. You may not be able to enter the correct strings by keyboard because your OS may not be configured to accept Arabic. However, you can download the files from the link at the top. Anyway, the resource file is included for reference:

We need to reference “Resources” project from our web application, so that we can read the resource strings right from our web site. Right click on “References” under our web project “MvcInternationalization”, and choose the “Resources” project from Projects tab.

Views

We need to extract the English text from all the views and move it to the resource files. Here is a trick, instead of typing the namespace name each time (e.g. “@Resources.Resources.LogOn “), we can add this namespace to the views Web.config and type “@Resources.LogOn” instead. Open the Web.config file under the views folder.

Determining Culture

There is a header field called “Accept-Language” that the browser sends on every request. This field contains a list of culture names (language-country) that the user has configured in their browser. The problem is that this culture may not reflect the real user’s preferred language, such as a computer in a public place. We should allow the user to choose a language explicitly and allow them even to change it. In order to do this sort of things, we need to store the user’s preferred language in a store, which can be perfectly a cookie. We will create a base controller that inspects the cookie contents first, if there is no cookie, we will use the “Accept-Language” field sent by their browser. Create a controller and name it “BaseController” like below:

    public class BaseController : Controller
    {
        protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
        {
            string cultureName = null;
            
            // Attempt to read the culture cookie from Request
            HttpCookie cultureCookie = Request.Cookies["_culture"];
            if (cultureCookie != null)
                cultureName = cultureCookie.Value;
            else
                cultureName = Request.UserLanguages != null && Request.UserLanguages.Length > 0 ? 
                        Request.UserLanguages[0] :  // obtain it from HTTP header AcceptLanguages
                        null;
            // Validate culture name
            cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe
            
            // Modify current thread's cultures            
            Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
            Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
            
            return base.BeginExecuteCore(callback, state);
        }
    }

Make sure all controllers in this project inherit from this BaseController. The base controller checks if the cookie exists, and sets the current thread cultures to that cookie value. Of course, because cookie content can be manipulated on the client side, we should always validate its value using a helper class called “CultureHelper”. If the culture name is not valid, the helper class returns the default culture.

CultureHelper Class

The CultureHelper is basically a utility that allows us to store culture names we are implementing in our site:

 

    public static class CultureHelper
    {
        // Valid cultures
        private static readonly List<string> _validCultures = new List<string> { "af", "af-ZA", "sq", "sq-AL", "gsw-FR", "am-ET", "ar", "ar-DZ", "ar-BH", "ar-EG", "ar-IQ", "ar-JO", "ar-KW", "ar-LB", "ar-LY", "ar-MA", "ar-OM", "ar-QA", "ar-SA", "ar-SY", "ar-TN", "ar-AE", "ar-YE", "hy", "hy-AM", "as-IN", "az", "az-Cyrl-AZ", "az-Latn-AZ", "ba-RU", "eu", "eu-ES", "be", "be-BY", "bn-BD", "bn-IN", "bs-Cyrl-BA", "bs-Latn-BA", "br-FR", "bg", "bg-BG", "ca", "ca-ES", "zh-HK", "zh-MO", "zh-CN", "zh-Hans", "zh-SG", "zh-TW", "zh-Hant", "co-FR", "hr", "hr-HR", "hr-BA", "cs", "cs-CZ", "da", "da-DK", "prs-AF", "div", "div-MV", "nl", "nl-BE", "nl-NL", "en", "en-AU", "en-BZ", "en-CA", "en-029", "en-IN", "en-IE", "en-JM", "en-MY", "en-NZ", "en-PH", "en-SG", "en-ZA", "en-TT", "en-GB", "en-US", "en-ZW", "et", "et-EE", "fo", "fo-FO", "fil-PH", "fi", "fi-FI", "fr", "fr-BE", "fr-CA", "fr-FR", "fr-LU", "fr-MC", "fr-CH", "fy-NL", "gl", "gl-ES", "ka", "ka-GE", "de", "de-AT", "de-DE", "de-LI", "de-LU", "de-CH", "el", "el-GR", "kl-GL", "gu", "gu-IN", "ha-Latn-NG", "he", "he-IL", "hi", "hi-IN", "hu", "hu-HU", "is", "is-IS", "ig-NG", "id", "id-ID", "iu-Latn-CA", "iu-Cans-CA", "ga-IE", "xh-ZA", "zu-ZA", "it", "it-IT", "it-CH", "ja", "ja-JP", "kn", "kn-IN", "kk", "kk-KZ", "km-KH", "qut-GT", "rw-RW", "sw", "sw-KE", "kok", "kok-IN", "ko", "ko-KR", "ky", "ky-KG", "lo-LA", "lv", "lv-LV", "lt", "lt-LT", "wee-DE", "lb-LU", "mk", "mk-MK", "ms", "ms-BN", "ms-MY", "ml-IN", "mt-MT", "mi-NZ", "arn-CL", "mr", "mr-IN", "moh-CA", "mn", "mn-MN", "mn-Mong-CN", "ne-NP", "no", "nb-NO", "nn-NO", "oc-FR", "or-IN", "ps-AF", "fa", "fa-IR", "pl", "pl-PL", "pt", "pt-BR", "pt-PT", "pa", "pa-IN", "quz-BO", "quz-EC", "quz-PE", "ro", "ro-RO", "rm-CH", "ru", "ru-RU", "smn-FI", "smj-NO", "smj-SE", "se-FI", "se-NO", "se-SE", "sms-FI", "sma-NO", "sma-SE", "sa", "sa-IN", "sr", "sr-Cyrl-BA", "sr-Cyrl-SP", "sr-Latn-BA", "sr-Latn-SP", "nso-ZA", "tn-ZA", "si-LK", "sk", "sk-SK", "sl", "sl-SI", "es", "es-AR", "es-BO", "es-CL", "es-CO", "es-CR", "es-DO", "es-EC", "es-SV", "es-GT", "es-HN", "es-MX", "es-NI", "es-PA", "es-PY", "es-PE", "es-PR", "es-ES", "es-US", "es-UY", "es-VE", "sv", "sv-FI", "sv-SE", "syr", "syr-SY", "tg-Cyrl-TJ", "tzm-Latn-DZ", "ta", "ta-IN", "tt", "tt-RU", "te", "te-IN", "th", "th-TH", "bo-CN", "tr", "tr-TR", "tk-TM", "ug-CN", "uk", "uk-UA", "wen-DE", "ur", "ur-PK", "uz", "uz-Cyrl-UZ", "uz-Latn-UZ", "vi", "vi-VN", "cy-GB", "wo-SN", "sah-RU", "ii-CN", "yo-NG" };
        // Include ONLY cultures you are implementing
        private static readonly List<string> _cultures = new List<string> {
            "en-US",  // first culture is the DEFAULT
            "es", // Spanish NEUTRAL culture
            "ar"  // Arabic NEUTRAL culture
           
        };
        /// <summary>
        /// Returns true if the language is a right-to-left language. Otherwise, false.
        /// </summary>
        public static bool IsRighToLeft()
        {
            return System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.IsRightToLeft;

        }
        /// <summary>
        /// Returns a valid culture name based on "name" parameter. If "name" is not valid, it returns the default culture "en-US"
        /// </summary>
        /// <param name="name" />Culture's name (e.g. en-US)</param>
        public static string GetImplementedCulture(string name)
        {
            // make sure it's not null
            if (string.IsNullOrEmpty(name))
                return GetDefaultCulture(); // return Default culture
            // make sure it is a valid culture first
            if (_validCultures.Where(c => c.Equals(name, StringComparison.InvariantCultureIgnoreCase)).Count() == 0)
                return GetDefaultCulture(); // return Default culture if it is invalid
            // if it is implemented, accept it
            if (_cultures.Where(c => c.Equals(name, StringComparison.InvariantCultureIgnoreCase)).Count() > 0)
                return name; // accept it
            // Find a close match. For example, if you have "en-US" defined and the user requests "en-GB", 
            // the function will return closes match that is "en-US" because at least the language is the same (ie English)  
            var n = GetNeutralCulture(name);
            foreach (var c in _cultures)
                if (c.StartsWith(n))
                    return c;
            // else 
            // It is not implemented
            return GetDefaultCulture(); // return Default culture as no match found
        }
        /// <summary>
        /// Returns default culture name which is the first name decalared (e.g. en-US)
        /// </summary>
        /// <returns></returns>
        public static string GetDefaultCulture()
        {
            return _cultures[0]; // return Default culture
        }
        public static string GetCurrentCulture()
        {
            return Thread.CurrentThread.CurrentCulture.Name;
        }
        public static string GetCurrentNeutralCulture()
        {
            return GetNeutralCulture(Thread.CurrentThread.CurrentCulture.Name);
        }
        public static string GetNeutralCulture(string name)
        {
            if (!name.Contains("-")) return name;
                
            return name.Split('-')[0]; // Read first part only. E.g. "en", "es"
        }
    }

 

We should populate “_cultures” manually. The “_cultures” dictionary stores the list of culture names our site supports. The nice part of this utility class is that it serves similar cultures. For example, if a user is visiting our site from the United Kingdom (en-GB), a culture which is not implemented in our site, he or she will see our site in English using “en-US” (English, United States). This way, you don’t have to implement all cultures unless you really care about currency, date format, etc. One important thing to mention is that we added all specific cultures for the Spanish language. For example, we are implementing “es” Spanish in general without any region where a date looks like this “30/07/2011”. Now suppose a nerd is coming from Chile (es-CL), it would be very nice display dates in their culture format (30-07-2011) instead of the neutral one. It does not matter if we don’t have a resource file for any of these cultures. ResourceManager can pick a neutral culture when it cannot find a specific culture file, this automatic mechanism is called fallback.

Controllers

Visual Studio has created a controller named “HomeCotnroller” for us, so we’ll use it for simplicity. Modify the “HomeController.cs” so that it looks like below:

 

    public class HomeController : BaseController
    {
        [HttpGet]
        public ActionResult Index()
        {
            return View();
        }
        [HttpPost]
        public ActionResult Index(Person per)
        {
            return View();
        }
        public ActionResult SetCulture(string culture)
        {
            // Validate input
            culture = CultureHelper.GetImplementedCulture(culture);
            // Save culture in a cookie
            HttpCookie cookie = Request.Cookies["_culture"];
            if (cookie != null)
                cookie.Value = culture;   // update cookie value
            else
            {
                cookie = new HttpCookie("_culture");                
                cookie.Value = culture;
                cookie.Expires = DateTime.Now.AddYears(1);
            }
            Response.Cookies.Add(cookie);
            return RedirectToAction("Index");
        }                
 
    }

 

The “SetCulture” action allows the user to change their current culture and stores it in a cookie called “_culture”. We are not restricted to cookies, we could instead save the culture name in Session or elsewhere, but cookies are really lightweight since they do not take any type of space on server side.

Creating a View Template

Now we will implement the View associated with the HomeController’s Index action. First delete the existing Index.cshtml file under “Views/Home” folder. Now to implement the view right-click within the “HomeController.Index()” method and select the “Add View” command to create a view template for our home page:

Click OK and replace the existing view if any. When we click the “Add” button, a view template of our “Create” view (which renders the form) is created. Modify it so it looks like below:

 

@model MvcInternationalization.Models.Person
@{
    ViewBag.Title = Resources.AddPerson;
    var culture = System.Threading.Thread.CurrentThread.CurrentUICulture.Name.ToLowerInvariant();
}
@helper selected(string c, string culture)
{
    if (c == culture)
    {
        @:checked="checked"
    }
}
<h2>@Resources.AddPerson</h2>
@using(Html.BeginForm("SetCulture", "Home"))
{
    <fieldset>
        <legend>@Resources.ChooseYourLanguage</legend>
        <div class="control-group">
            <div class="controls">
                <label for="en-us">
                    <input name="culture" id="en-us" value="en-us" type="radio" @selected("en-us", culture) /> English
                </label>
            </div>
        </div>
        <div class="control-group">
            <div class="controls">
                <label for="es">
                    <input name="culture" id="es" value="es" type="radio" @selected("es", culture) /> Español
                </label>
            </div>
        </div>
        <div class="control-group">
            <div class="controls">
                <label for="ar">
                    <input name="culture" id="ar" value="ar" type="radio" @selected("ar", culture) /> العربية
                </label>
            </div>
        </div>
      
    </fieldset>
       
    
    
}
@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()
    
    <div class="form-horizontal">        
        <hr />
        @Html.ValidationSummary(true)
        <div class="form-group">
            @Html.LabelFor(model => model.FirstName, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.FirstName)
                @Html.ValidationMessageFor(model => model.FirstName)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.LastName, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.LastName)
                @Html.ValidationMessageFor(model => model.LastName)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Age, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Age)
                @Html.ValidationMessageFor(model => model.Age)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Email, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Email)
                @Html.ValidationMessageFor(model => model.Email)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Biography, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Biography)
                @Html.ValidationMessageFor(model => model.Biography)
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="@Resources.Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
    <script type="text/javascript">
        (function ($) {
            $("input[type = 'radio']").click(function () {
                $(this).parents("form").submit(); // post form
            });
            
        })(jQuery);
    </script>
}
    

 

The javascript code simply post back the form to set the culture. The “selected” helper is used to mark the appropriate culture radio button as checked.

Of course, we should not forget about partial views too

 

@using Microsoft.AspNet.Identity
@if (Request.IsAuthenticated)
{
    using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))
    {
    @Html.AntiForgeryToken()
    <ul class="nav navbar-nav navbar-right">
        <li>
            @Html.ActionLink(User.Identity.GetUserName(), "Manage", "Account", routeValues: null, htmlAttributes: new { title = "Manage" })
        </li>
        <li><a href="javascript:document.getElementById('logoutForm').submit()">@Resources.LogOff</a></li>
    </ul>
    }
}
else
{
    <ul class="nav navbar-nav navbar-right">
        <li>@Html.ActionLink(Resources.Register, "Register", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })</li>
        <li>@Html.ActionLink(Resources.LogOn, "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
    </ul>
}
    

 

Left-to-right or Right-to-left

HTML supports rtl languages too, so we need to make sure our main HTML tag has the appropriate direction. Modify the _Layout.cshtml file to look like the following:

 

        
 <!DOCTYPE html>
<html lang="@CultureHelper.GetCurrentNeutralCulture()" dir="@(CultureHelper.IsRighToLeft() ? "rtl" : "ltr")">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - ASP.NET MVC Internationalization</title>
    @Styles.Render("~/Content/css" + (CultureHelper.IsRighToLeft() ? "-rtl" : ""))
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("ASP.NET MVC Internationalization", "Index", "Home", null, new { @class = "navbar-brand" })
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">                    
                </ul>
                @Html.Partial("_LoginPartial")
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>@DateTime.Now</p>
        </footer>
    </div>
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap" + (CultureHelper.IsRighToLeft() ? "-rtl" : ""))
    @RenderSection("scripts", required: false)
</body>
</html>
    

 

Now we need to provide basically two sets of CSS and JS files: One for left-to-right languages and one for right-to-left languages. Because MVC default template is using Bootstrap, we need to install the RTL version of bootstrap files. For this, open the package manager console (aka Nuget) by choosing Tools -> Library Package Manager -> Package Manager Console and type:

Install-Package Twitter.Bootstrap.RTL

Make sure you have the two files bootstrap-rtl.js and bootstrap-rtl.css

We need to create two bundles: one for RTL and one for LTR. Modify the file BundleConfig.cs and add the following:

 

 
           bundles.Add(new ScriptBundle("~/bundles/bootstrap-rtl").Include(
                      "~/Scripts/bootstrap-rtl.js",
                      "~/Scripts/respond.js"));

            bundles.Add(new StyleBundle("~/Content/css-rtl").Include(
                      "~/Content/bootstrap-rtl.css",
                      "~/Content/site.css"));
    

 

Try It Out

Run the website now. Notice that client side validation is working nicely. Click on radio buttons to switch between cultures, and notice how right-to-left language is showing correctly. Using separate views allowed us to control how to position elements, and have made our views clean and readable.


English


Spanish


Arabic

How to store culture in the URL instead of a cookie?

There can be different reasons why you want the culture to be part of your website url (such as search engine indexing). Anyway. First let’s fist define the culture to be part of our routes. Edit RouteConfig.cs like below:

 

 
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            routes.MapRoute(
                name: "Default",
                url: "{culture}/{controller}/{action}/{id}",
                defaults: new {culture = CultureHelper.GetDefaultCulture(), controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }

 

Notice the we used the default culture in case it is missing. Now modify the base controller:

 

 
        protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
        {
            string cultureName = RouteData.Values["culture"] as string; 

            // Attempt to read the culture cookie from Request
            if (cultureName == null)               
                cultureName = Request.UserLanguages != null && Request.UserLanguages.Length > 0 ? Request.UserLanguages[0] : null; // obtain it from HTTP header AcceptLanguages

            // Validate culture name
            cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe


            if (RouteData.Values["culture"] as string != cultureName) {
                
                // Force a valid culture in the URL
                RouteData.Values["culture"] = cultureName.ToLowerInvariant(); // lower case too

                // Redirect user
                Response.RedirectToRoute(RouteData.Values);                
            }
          

            // Modify current thread's cultures            
            Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
            Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;


            return base.BeginExecuteCore(callback, state);
        }

 

The final step is to modify the SetCulture action in HomeController.cs

 

        
        public ActionResult SetCulture(string culture)
        {
            // Validate input
            culture = CultureHelper.GetImplementedCulture(culture);
            RouteData.Values["culture"] = culture;  // set culture

           
            return RedirectToAction("Index");
        }     
    

NOTE: To force the default culture appear in the URL, simply set the default value for culture in RouteConfig.cs to string.Empty

Summary

Building a multilingual web application is not an easy task. but it’s worth it especially for web applications targeting users from all over the world, something which many sites do. It is true that globalization is not the first priority in site development process, however, it should be well planned early in the stage of development so it can be easily implemented in the future. Luckily, ASP.NET supports globalization and there are plenty of .NET classes that are handy. We have seen how to create an ASP.NET MVC application that supports 3 different languages, including a right-to-left one, which requires a different UI layout. Anyway, here is a summary of how to globalize a site in ASP.NET MVC:

 

  1. Add a base controller from which all controllers inherit. This controller will intercept the view names returned and will adjust them depending on the current culture set.
  2. Add a helper class that stores the list of culture names that the site will support.
  3. Create resource files that contain translation of all string messages. (e.g. Resources.resx, Resources.es.resx, Resources.ar.resx, etc )
  4. Update views to use localized text.
  5. Localize javascript files.

 

I hope this helps!
Any questions or comments are welcome!

Setting Up PHP behind Nginx with FastCGI

The traditional way of running PHP is with Apache HTTP Server using mod_php. In fact, mod_php was the most popularApache module up until 2009 when that claim went to mod_ssl. But as the Internet grew and the technologies that power it evolved, other ways of serving PHP sites and applications became available. nginx, a server written to solve the C10k problem, is eating into Apache’s marketshare, and running PHP behind nginx with FastCGI is becoming an increasingly commonplace alternative.

Configuring a PHP and nginx setup is a bit more involved than the traditional Apache one, although it has become easier in the recent past with better packaging and default configurations. This article will guide you through the process of configuring PHP and nginx for serving up your own high performance website. I assume you’re working on Ubuntu Server(I’m using Ubuntu 13.04, 64-bit), and we’ll install the core applications using Apt.

FastCGI in a Nutshell

In the early days of the web, developers needed a way to run scripts on the server to generate page content and spawn other processes, and so the CGI (Common Gateway Interface) protocol was devised as a means to facilitate this.

CGI defines the environment (environment variables, request-specific variables, etc.) a script will execute in, and how data is passed between the script and the web server. For each request, CGI sets up the environment, spawns a running instance of the script and passes any incoming data to it, and captures and sends the script’s output to the server.

nginx-fcgi-1a

Of course, CGI has its pros and cons. On the pro side, CGI is language-independent, meaning scripts can be written in any programming language the developer is comfortable with (for example: Perl, C, bash, etc.), and each execution runs in isolation from the web server which prevents bugs in a script from potentially crashing the entire web stack. On the con side, a new process is created for each request which can be CPU-intensive and time-consuming.

FastCGI is essentially CGI with some enhancements that addresses CGI’s shortcomings. It reduces time/CPU-overhead by using a persistent process to execute scripts, and data between the web server and FastCGI is passed using sockets which encourages more-scalable service architectures (server farms and load balancing, asynchronous communication between web server and FastCGI, etc.).

For more information on FastCGI, check out the white paper on the FastCGI website, and for PHP’s implementation specifically check out the PHP manual.

Basic Installation and Configuration

To install PHP via Apt, execute the following at the command-line:

sudo apt-get install php5-cli php5-fpm

Then, to install nginx, execute:

sudo apt-get install nginx

Apt will identify the required dependencies in each case and prompt for confirmation to continue. Once granted, Apt will download and install the requested packages and their dependencies.

Ubuntu places nginx’s configuration files in /etc/nginx and its sub-directories. Shared configuration fragments are kept in that root, and specific server setups reside in sites-available with symlinks in sites-enabled to make them active.

It’s a good idea to avoid editing the original configuration files, so I suggest using copies and keeping the originals pristine. This way, you can configure nginx to your liking and not worry about your efforts being over-written by Apt during future upgrades. You also have the original default configuration to refer back to if you never need to.

Delete the symlink in sites-enabled, duplicate the default file in sites-available, and create a new symlink.

cd /etc/nginx
sudo rm sites-enabled/default
sudo cp sites-available/default sites-available/my-default
sudo ln -s /etc/nginx/sites-available/my-default sites-enabled/default

To have nginx proxy PHP requests to the FastCGI service, open the freshly duplicated configuration file and locate the section that starts with:

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {

Uncomment the location line and it’s matching close brace, the fastcgi_split_path_info line, and the lines that pertain to running with php5-fpm. When you’re done, the section should look like this:

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
location ~ \.php$ {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
#   # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
#
#   # With php5-cgi alone:
#   fastcgi_pass 127.0.0.1:9000;
#   # With php5-fpm:
    fastcgi_pass unix:/var/run/php5-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;
}

Save your changes, and then start nginx.

sudo service nginx start

Connect to the server with your browser and you should see nginx’s welcome screen.

nginx-fcgi-2

The default configuration specifies /usr/share/nginx/html as the web root. In that directory, create a file namedinfo.php with a call to phpinfo() and then load it in your web browser to verify nginx can communicate with PHP. In the output, you should see the Server API listed as “FPM/FastCGI”.

nginx-fcgi-3

Additional Configuration

Congratulations! You have a basic nginx and PHP install set up and serving files! But there are a few additional configuration steps that I recommend.

Web Root Permissions

A quick check of the web root’s permissions shows that it’s not writable by anyone other than root. Constant sudo-ing grows tiresome, and tinkering around as root is generally a bad idea, so I recommend executing the following:

sudo adduser <username> www-data
sudo chgrp -R www-data /usr/share/nginx/html
sudo chmod -R g+rw /usr/share/nginx/html
sudo chmod g+s /usr/share/nginx/html

adduser adds your username (replace <username> with your own login) to the www-data group, the same group that nginx runs under in the default Ubuntu install. chgrp recursively updates the html directory and its children to belong to the www-data group.

The first chmod command then grants read and write group privileges recursively to html and its children, and the second sets the SGID bit so that any files or directories created in html will take on the www-data group as its group owner. Note that the second chmod is not run recursively.

After the four commands have been run, you’ll need to reload your shell for the group association on your user account to take effect. Log out and then back in again. Once you’re in, you’ll be able to create, edit, and delete files in the web root to your heart’s content under your standard login without any escalated privileges.

Non-existent Scripts

I also recommend adding a try_files directive somewhere in the location block you uncommented earlier in the configuration, like this:

location ~ \.php$ {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    try_files $uri $uri/ =404;
...

This protects you from a known vulnerability that results from an incorrectly configured system where nginx tries various patterns to satisfy a request. You may want to tweak the exact list of values after reading the documentation fortry_files and take into consideration the needs of your application, but the above should provide a minimum level of protection.

Porting .htaccess Files

At some point you’ll want to serve an existing PHP application that was previously served by Apache, and most likely it will have some configuration details for the server either in an .htaccess file or in Apache’s static configuration files (mod_rewrite directives, for example). There are a couple online converters that “translate” Apache directives to nginx, and these are a good starting point in porting your configuration. But be careful not to rely on them entirely. As the saying goes, “Pasting code from the Internet into production is like chewing gum found in the street.” Familiarize yourself with nginx’s documentation and double check the converter’s output to make sure everything is in order.

Conclusion

The traditional way of running PHP (Apache and mod_php) is stable and mature, and for the majority of applications is an excellent platform. But the landscape is changing, and more performant solutions like nginx are gaining widespread acceptance, even eating into Apache’s market share. In this article you saw how to configure PHP to run behind nginx with FastCGI. And with your shiny new installations ready to server your own high performance website to the masses, I wish you success!