ASP.NET MVC baru memasuki versi 1.0 dan harus kita maklumi beberapa fitur masih belum memiliki garis yang jelas, dalam artian masih digodok dalam MvcFutures atau masih bisa meminjam implementasi di Web Form. Salah satunya adalah implementasi globalisasi. Kalau di Web Form kita sudah bisa membaca satu pedoman yang jelas bagaimana mengimplementasikan globalisasi, di ASP.NET MVC bacaan ini belum ada. Dalam salah satu posting berseri StoreFront, Rob Conery mencoba membahas implementasi globalisasi dalam ASP.NET MVC. Posting Rob bisa dibaca di sini.
Dalam posting ini saya akan mencoba mengetengahkan salah satu implementasi globalisasi di ASP.NET MVC dengan contoh kasus tertentu. Dalam contoh kasus ini kita punya dua domain misalnya www.mydomain.net dan www.mydomain.co.id. Jika seseorang mengakses domain .net, maka bahasa Inggris lah yang akan digunakan sebagai bahasa website tersebut baik dari sisi content maupun label. Sebaliknya jika domain .co.id yang diakses maka bahasa Indonesia lah yang akan disajikan. Untuk menyajikan content multibahasa tersebut hanya ada satu website dengan satu database saja.
OK, kalau berkaitan dengan fitur multi bahasa dalam content, tentu kita harus menyimpan multiple record untuk content yang sama dalam database. Terdapat dua design-decision untuk melakukan hal ini. Salah satunya adalah dengan menambahkan satu field penanda bahasa yang lazim disebut dengan culture id. Dalam gambar berikut ini, tabel News ditambahkan column CultureId sebagai penanda bahasa. Pemilihan design decision dengan cara demikian berarti kita membolehkan news id yang berbeda untuk item yang 'dianggap sama' dalam bahasa yang berlainan.
Berikutnya adalah menemukan cara bagaimana agar akses dari domain .net dan domain .co.id bisa diidentifikasi. Informasi ini tentunya terdapat dalam host header dari setiap web request. Kalau kita periksa HttpContext, kemudian periksa lebih lanjut object Request, di sana ada satu collection yang bernama Headers. Collection ini berbentuk dictionary, dan kita bisa mengakses informasi tentang host dari Request.Headers["Host"].
Dalam ASP.NET MVC, HttpContext ini available sebagai salah satu property dari Controller. Oleh karena itu dalam action-method kita bisa membaca informasi ini dan menentukan bahwa Culture Id dari content yang akan kita sajikan berbahasa Inggris atau Indonesia sesuai dengan informasi host header. Namun kalau kita melakukan hal ini dalam setiap action method, berarti semua action method akan diawali dengan pemanggilan method untuk mendapatkan informasi Culture Id ini, seperti terlihat dalam contoh source code di bawah ini.
9 [HandleError]
10 public class HomeController : Controller
11 {
12 public ActionResult Index()
13 {
14 CheckCultureId();
15 return View();
16 }
17
18 public ActionResult Browse()
19 {
20 CheckCultureId();
21 return View();
22 }
23
24 public ActionResult About()
25 {
26 CheckCultureId();
27 return View();
28 }
29 }
Perhatikan dalam source code di atas bahwa kita selalu memanggil method CheckCultureId() dalam setiap action-method. Hal ini tentu saja ugly selain memungkinkan kita kelupaan memanggil method tersebut dalam salah satu action-method. Untungnya ASP.NET MVC memiliki satu fitur yang bernama filter. Dengan filter ini sebuah class bisa di-dekorasi dengan atribut tertentu yang bisa mengontrol behavior pemanggilan action method. Sebuah filter memiliki method OnActionExecuting(), OnActionExecuted(), dan lain-lain yang menunjukkan action-apa saja yang 'otomatis' dilakukan di setiap action method.
Agar lebih efektif menyimpan informasi Culture Id yang nantinya dikerjakan dalam sebuah filter, kita perlu memiliki sebuah base class dari controller-controller yang nantinya akan kita buat. Base class berisi state-state dan behavior yang secara umum dimiliki oleh semua controller, termasuk Culture Id ini. Secara hirarki diagram class dari controller kita akan tampak seperti dalam gambar berikut ini.
Dengan struktur class seperti di atas, kita bisa memiliki implementasi filter Culture Id seperti terlihat dalam source code di bawah ini.
public class CultureIdFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = filterContext.Controller as BaseController;
var httpContext = filterContext.RequestContext.HttpContext;
if (httpContext == null)
return;
var host = httpContext.Request.Headers["Host"];
if (string.Compare(host, Constants.BahasaHostHeader,
StringComparison.OrdinalIgnoreCase) == 0)
{
controller.CultureId = "id-ID";
controller.ViewData["CultureId"] = "id-ID";
}
else
{
controller.CultureId = "en-US";
controller.ViewData["CultureId"] = "en-US";
}
}
}
Sehingga kita bisa memakai filter tersebut dalam setiap controller, seperti contoh pemakaian filter dalam NewsController di bawah ini. Perhatikan bahwa atribut CultureIdFilter ini bisa kita terapkan dalam class ataupun masing-masing action-method. Jika diterapkan dalam deklarasi class, artinya setiap action-method dari class tersebut akan secara otomatis diterapkan filter ini.
11 [HandleError]
12 [CultureIdFilter]
13 public class NewsController : BaseController
14 {
15 private readonly INewsRepository newsRepository;
16
17 public NewsController(ISettingRepository settingRepository,
18 INewsRepository newsRepository): base(settingRepository)
19 {
20 this.newsRepository = newsRepository;
21 }
Jika Culture Id sudah ketahuan berarti tinggal masalah passing informasi ini ke 'data service' sebagai salah satu parameter data retrieval. Dalam contoh di bawah ini kita menggunakan Entity Framework yang dibungkus dalam sebuah repository. Berikut ini adalah source code dari NewsRepository. Perhatikan bahwa Culture Id menjadi parameter dari setiap method yang mengambil data.
public class NewsRepository: INewsRepository
{
public const int LatestNewsCount = 3;
private EfindoEntities entities;
public NewsRepository(EfindoEntities entities)
{
this.entities = entities;
}
public IList<News> GetLatestNews(string cultureId)
{
return (
from n in entities.News
where n.CultureId == cultureId
&& n.Status == Constants.Published
select n).Take(LatestNewsCount).ToList();
}
public IPagedList<News> GetAllNews(string cultureId, int pageIndex,
int pageSize)
{
return (
from n in entities.News
where n.CultureId == cultureId
&& n.Status == Constants.Published
orderby n.NewsDate descending
select n).ToPagedList(pageIndex, pageSize);
}
public News GetNewsByName(string cultureId, string name)
{
return (
from n in entities.News
where n.CultureId == cultureId
&& n.Name == name
select n).FirstOrDefault();
}
}
OK, semoga metode yang saya sajikan dalam posting yang singkat ini bisa menjadi salah satu pertimbangan Anda mengimplementasikan content multibahasa pada sebuah website ASP.NET MVC.