Serenity Dersleri

Serenity Nedir?

Serenity .NET ve Javascript tabanlı açık kaynak açık kaynak bir web uygulama altyapsıdır. İçinde birçokaçık kaynak projeyi çeşitli noktalarda kullanmış ve işin çoğunu yapıp yeni bir projeye başlayan kişiyi tekerleği yeniden icat etmekten kurtarmayı hedeflemiştir.


Temel Dökümantasyon

Serenity için projenin sahibi olan sevgili Volkan Ceylan şurada oldukça güzel bir dökümanstasyon hazırlamış durumda. Bizim bu dökümanı ayrıca hazırlamamızın amacı:

  • Türkçe döküman ihtiyacını karşılamak
  • Temel dökümantasyonda atlanan ancak projeyi ilk defa kullananlar için kapalı olabilecek konuları aydınlatmak
  • Proje içinde kullanılan Salterelle gibi Türkiyede çok da popüler olmayan teknolojileri satıriçi açıklamak. Bunlar için kod örnekleri bulmak her zaman mümkün olmayabiliyor
  • Nasıl yapılır? formatına uygun öğretici içerikler hazırlamak.
  • İngilizce konusunda yetersiz ancak hevesli yerli geliştiricilerin bu topraklardan çıkan bir altyapıdan faydalanmasını sağlamak. Halihazırda Serenity bir çok yabancı geliştirici tarafından kullanılsa da Türkiyede bilinmiyor.

SERENE Nedir?

Serenity yalın haliyle bir Asp.NET projesidir ve bir Asp.NET projesi için Visual Studioda halihazırda bir başlangıç şablonları zaten bulunmaktadır. Ancak Serenity farklı bir kullanım tarzı önermekte ve bu şekilde birçok işi zaten kendisi üstlenmektedir. Bu sebeple Serenity altyapısını kullanan SERENE isimli proje şablonu bizim başlangıç noktamız olacaktır.

Kendi içinde basit bir yönetim paneli, kullanıcı, rol, yetki mekanizması, çoklu dil ve tema desteği bulunduran bu şablon sayesinde projeniz şablondan başlatıldığında bile epey bir yol katetmiş oluyor. Siz de bir backoffice uygulamasındaki standart girdi-çıktı ekranları, raporlar ve iş süreçlerini kodlamak kalıyor.

Şablonun Kurulumu

Aşağıdaki adreslerde ekran görüntüleri ile açıklandığı şekilde SERENE'yi kurabilirsiniz.

Visual Studio galerisinden kurulum

Doğrudan Visual Studio içinden kurulum

Şablonu Tanıyalım

Şablon 2 projeden oluşmaktadır:

  • ProjeAdi.Script
  • ProjeAdi.Web

Script projesi

Script Projesi projemizin arabirim kodlarının olduğu bölüm. Normal bir Asp.Net ya da MVC projesindeki gibi arabirim üzerine HTML, CSS ve MVC viewler ile çalışmıyoruz. Bunun yerine Script projesinde Salterelle Compiler üzerinde derlenen C# koduyla çalışıyor, doğrudan ne HTML'ye ne de Javascripte temas etmiyo olacağız. Bu da bizi C#'tan başka bir dile sıçramadan daha düz bir yolda ilerlememizi sağlayacak. Yine -ilerde göreceğimiz üzere- otomatik oluşturulan ekranlar sayesinde her sayfada tasarımla uğraşmak yerine işimize odaklanacağız.

Script projesinin gerçek bir c# projesi olmadığını söylesek yanlış olmaz sanırım. Bu projenin görevi arabirim tarafını üretmek diyebiliriz.

Bu noktada geç de olsa bahsedeyim Serenity bir MVC projesi ancak Script projesi ile bu harflerden biri V(iew*) konusundan bir miktar uzaklaşmış oluyoruz. Eğer bir web sayfası yapıyor olsaydık belki bu bir sorun olabilirdi ama backoffice uygulaması altyapısı için bu harika bir özellik: tasarımla uğraşma ama şık ve standart bir tasarımın olsun :)

Script projesi dizinleri:

  • Administration: Yönetim modülünün ekran/arabirim kodları bu dizin altında bulunuyor
  • Common:Ortak işlevler içingerekli birkaç editör kodu
  • Imports:Otomatik kod oluşturma (tt) dosyaları bu dizin altında
  • Membership:Login ekranı bu dizin altında
  • Northwind:Microsoftun ürünlerinde temel işlevleri gösterebilimek için kullandığı sanal bir Northwind şirketi veritabanı ekranları. Bu veritabanını neredeyse bütün microsoft tanıtım/seminer/eğitimlerinde görebilirsiniz. Burada yer almasının sebebi basit: serenity nasıl çalışıyor göstermek için. Siz şablonu kurduğunuzda Nortwind isimli bir modül ve bu modül altında gayet de herşeyi çalışan ekranlar buluyorsunuz. Örnek kod arıyorsanız ilk bakacağınız yer bu dizin olabilir. Gerçi serenity herşeyi kendi yaptığı için çok fazla kod olduğu da söylenemez :) Çalışmaya başladıktan sonra bu dizini silebilirsiniz.

XXXDialog.cs

Kayıt düzenleyen popup ekranın arabirimini oluşturan sınıftır.

XXXGrid.cs

Kayıtları listeleyen grid/liste ekranının arabirimini oluşturan sınıftır.

Web Projesi

Web projesini normal bir Asp.Net projesi gibi düşünebilirsiniz ancak kendi içinde daha sofistike görevleri olan bölümlerden oluşmaktadır. Her ne kadar proje yapısı büyüdükçe bu bölümleri kendi projelerine almak mümkünse de başlangıç olarak bu şekilde derli toplu olması tercih edilmiş.

Web projesinde normal bir Asp.Net projesindeki gibi arabirim üzerinde çalışmıyoruz dedik. Burada sunucu taraflı kodlarımız (MVC için Controller), veritabanındaki tablolarımızın karşılığı olan entity classlarımız (MVC için Model) ve diğer kodlar bulunmakta.

Web projesi dizinleri:

  • App_Data: Malumunuz bu dizin Asp.Net projelerinde veri tutmak için kullanılan özl bir klasör. Serene şablonunda da bu dizin altında varsayılan database oluşturulup LocalDb ile erişilebilir durumda oluyor.
  • App_Start: Uygulama başlatma, konfigürasyon, migration gibi işleri için gerekli sınıflar yer alır.
  • Content: CSS dosyalarının yer aldığı dizin.Uygulama bir çok açık kaynak kütüphanesine referans vermektedir.
  • fonts: Font kütüphanelerinin yer aldığı dizin
  • Scripts: Javascript dosyalarının yer aldığını dizin. Uygulama bir çok açık kaynak kütüphanesine referans vermektedir.
  • tools: Açık kaynak kütüphane çalıştırılabilir dosyaları.
  • Views: Asp.NET MVC'ye ait şablon view dosyaları. Serene ilk başta iki temel şablon olan dialog ve grid formatını kullanıyor. Dialog bir popu içinde kayıtların düzenlendiği grid ise liste/arama ekranlarında kullanılan şablon. İhtiyaca göre yeni ekran formatları eklenip buraya konabilir.
  • Modules: Bu dizini en sona sakladım. Çünkü diğerler dizinlerde eğer temel işlevlerle yetiniyorsanız herhangi bir değişiklik yapmanız gerekmiyor. Ancak eklediğimiz yeni liste ve düzenleme formları bu dizin altında tutuluyor.

Modules Dizini

Bu dizin altındaki her bir dizin uygulamada bir modül olarak görünmektedir. Örneğin aşağıda solution explorerdaki dosya yapısı ile çalışan projenin modül yapısının karşılığı gösterilmiştir.

Modül altındaki her bir dizin bir ekrana karşılık gelmektedir. Temel olarak her bir ekran bir liste/grid olarak açılır ve bu listeye yeni bir popup düzenleme ekranı ile yeni kayıt eklenir, mevcut kayıt düzenlenir ya da silinir.

Bir ekran için gerekli dosyalar aşağıdaki gibidir.

XXXColumns.cs

Griddeki kolonların belirtildiği sınıftır. Yalnızca kolonlar property olarak gösterilir ve girdde görünmesi sağlanır.

XXXEndPoint.cs

Grid için sağlanan servis ednpointidir.

XXXForm.cs

Kaydın düzenlendiği form için alanların belirtildiği sınıftır. Her bir alan yalnızca property olarak belirtilerek formda alanın görünmesi sağlanmaktadır.

XXXIndex.cshtml

View dosyasıdır.

XXXPage.cs

Controller dosyasıdır.

XXXRepository.cs

Repository patterne uygun şekjilde business kodlarının konumlandırıldığı dosyadır. Servis tabanlı bütün iş kuralları burada kodlanmalıdır.

XXXRow.cs

Veritabanı tablosunun c# taraflı map edilmiş dosyasıdır. Her bir kolonun property olarak karşılığı yer alır.

Alanlarda Kullanılan Attribute'ler

Serenity dekleratif yapıya sahip bir framework'tür. Yani çalışma zamanında olmasını istediğiniz şey için kod yazmak yerine bu çalışma şeklini sağlayacak tanımlamaları yaparak çözüm üretir. Söz gelimi kayıt düzenlediğiniz dialog ekranlarında bir alanın disabled olması için formun açılışndan sonra ilgili alanı bulup disable edecek kodu da yazabilirsiniz ya da Serenity tarzında o alanın disabled olduğunu bir defa söyler ve sisteme o şekilde tanıttığınız için başka da kod yazmazsınız. Bu yaklaşımı sağlayacak şekilde serenity içinde birçok attribute bulunmaktadır. Dialog ekranları için XXXForm.cs grid ekranları için XXXColumns.cs dosyalarındaki propertylere attributeler ile gerekli tanımların yapılarak

Not: Resimde görülen CustomerCountryLookup.cs özel bir editördür, her dizinde yer almaz, ilerde editörler konusunda değinilecek. Bütün bu dosya türleri her ekranda kendi adıyla yer alacaktır. Bir miktar çok göründüğünün farkındayım ancak neyse ki Serenity.CodeGenerator (sergen) aracı ile bütün bu dosyalar için dert etmemiz gerekmiyor.

Serenity.CodeGenerator (sergen.exe)

Yukarıda madde madde her bir ekran için gerekli olan dosyaları gördük. Bu kadar çok dosyanın olması, her birinin teker teker üretilmesi ve yazılacak bir sürü standart kod gözünüzü korkutmasın. Bizim için bu işi yapan bir araç var: Sergen.exe

Sergen ProjeAdaDizini/packages/Serenity.CodeGenerator.x.x.x/tools/sergen.exe

konumunda bulunan bir kod üretim aracıdır.

Resimde görüldüğü gibi; Script ve web projelerinin lokasyonunu istedikten sonra sizden modül ve ana namesace biglisini istiyor. ConnectionString'i proje dosyasından otomatik çekiyor ve alttaki combo'ya veritabanındaki tabloları dolduruyor. Bir tabloyu seçip projedeki adını Entity Identifier alanına girdiğinizde sizin için gerekli kodları üzeriyor.

Root Namespace projenizin ana namespace'idir ve muhtemelen proje adınızdır.

Module Name tabloyu projenizde konumlandıracağınız modülün adıdır.

Connection Key Eğer birden fazla veritabanıyla çalışmıyorsanız burası Default gelecektir ve değiştirmenize gerek kalmayacktır.

Entity Identifier tablonuzun kodlarınızdaki maplenmiş karşılığı olan sınıfın (Entity) isim karşılığıdır, genellikle tablo adı ile aynı olur.

Permission Key bu tabloya ait oluşturulan ekranlardaki yetki key'leridir. Her bir tablo için (okuma ve yazma için ayrı ayrı) yetki key'leri verebilir ve buna göre detaylı yetkilendirme yapabilirsiniz.

Ekran Tasarımları

Gride Kolon Eklemek

Forma (Dailoga) Alan Eklemek

Lookup combosu oluşturmak

Enum'u lookup olarak kullanmak

Formda ve girdde alan değerini değil enum karşılığını göstermek ve dialogda enum değerlerinden oluşan lookup editörünü kullanmak için Row'da alanın Int16/Int32/In64 alanlarının tiplerini Enum tipiyle değiştirmeniz yeterlidir.

        [DisplayName("İşlem Türü")]
        public Int32? IslemTuru
        {
            get { return Fields.IslemTuru[this]; }
            set { Fields.IslemTuru[this] = value; }
        }

Şu şekilde değiştirilmeli

        [DisplayName("İşlem Türü")]
        public IslemTuruId IslemTuru
        {
            get { return (IslemTuruId)Fields.IslemTuru[this]; }
            set { Fields.IslemTuru[this] = (Int32)value; }
        }

Lookup üzerinde inplace editor ile eleman ekleme

Bir combo ile detay tablo kayıtlarının listelendiği alanda o detay kaydına yeni bir eleman eklemek için aşağıdaki InplaceAdd attribute'i kullanılabilir.

        [DisplayName("Supplier Id"), ForeignKey("Suppliers", "SupplierID"), LeftJoin("sup")]
        [LookupEditor(typeof(SupplierRow), InplaceAdd = true)]
        public Int32? SupplierID
        {
            get { return Fields.SupplierID[this]; }
            set { Fields.SupplierID[this] = value; }
        }

Kolonu hızlı aramaya dahil etmek

Griddeki hızlı arama özelliği QuickSearch attribute'i ile işaretlenmiş alanlarda arama yapacaktır.

        [DisplayName("Product Name"), Size(40), NotNull, QuickSearch, LookupInclude]
        public String ProductName
        {
            get { return Fields.ProductName[this]; }
            set { Fields.ProductName[this] = value; }
        }

Client Tarafı-Javascript'siz Client-Side Kod geliştirmek

"Q" Kütüphanesi

Client-Side'da Q. ile başlayıp bir dizi javascript işlevini C# tarafında kodlayabilirsiniz.Örnek:

Javascript C#
alert('mesaj') Q.Alert('Mesaj')
console.log('log mesajı') Q.Log('log mesajı')
NotifySuccess
Confirm

Daha fazla bilgi için:

Loglama

Bir tablonun loglanması için ilgili tablonun Row classını şu interfacelerden implemente etmelisiniz. Interface'e ait getter property implemente edildikten sonra ilgili alanlar tablonuzdaki uygun fieldler ile maplenmelidir.

IUpdateLogRow : Tabloda bulunan/bulunması gereken UpdateUserId ve UpdateDate alanları otomatik olarak güncelleyen kullanıcı ve güncelleme tarihi ile dolacaktır.

IInsertLogRow : Tabloda bulunan/bulunması gereken InsertUserId ve InsertDate alanları otomatik olarak ekleyen kullanıcı ve ekleme tarihi ile dolacaktır.

IDeleteLogRow : Tabloda bulunan/bulunması gereken DeleteUserId ve DeleteDate alanları otomatik olarak silen kullanıcı ve silinme tarihi ile dolacaktır.

ILoggingRow : IUpdateLogRow ve IInsertLogRow interfacelerini beraber kullanmak için eklenmiştir.

CaptureLogAttribute

CaptureLog attribute ve ICaptureLogRow ile ilgili detay bekleniyor

Validasyon

Serenity yazılım geliştiricilerine standart bir validasyon yaklaşımı sunar.

İstemci Taraflı Validasyon

Uygulama daha sunucuya gitmeden bazı temel validasyonlar yapmak isteyebilirsiniz. Bu durumda aşağıdaki örnekte görüldüüğü gibi dialogtaki her bir alan için özel validasyon olayları yazabilirsiniz.

public class UserDialog : EntityDialog<UserRow>
    {
        private UserForm form;

        public UserDialog()
        {
            form = new UserForm(this.IdPrefix);

            form.Password.AddValidationRule(this.uniqueName, e =>
            {
                if (form.Password.Value.Length < 7)
                    return "Password must be at least 7 characters!";

                return null;
            });

            form.PasswordConfirm.AddValidationRule(this.uniqueName, e =>
            {
                if (form.Password.Value != form.PasswordConfirm.Value)
                    return "The passwords entered doesn't match!";

                return null;
            });
        }
    }

Sunucu Taraflı Validasyon

Sunucu tarafında validasyon:

throw DataValidation.RequiredError(fld.DisplayName.Name, fld.DisplayName.Title);

ya da

 throw new ValidationError("UniqueViolation", "Username",
                        "A user with same name exists. Please choose another!");

şeklinde olabilir. DataValidation altında bir dizi doğrulama işlemi için standart operasyonlar gruplanmıştır. Bu standart işlemler dışında iş kuralları gereği verilemesi gereken doğrulama mesajları ve işkesen uyarılar için ValidationError sınıfı kullanılacaktır.

validasyonlar için bu sınıfların kullanılması önemlidir. Zira serenity istemci tarafında bu sınıflardan dönen mesajlara göre kullanıcının anlayabileceği şekilde mesajları yönetmekte ve kullanıcıya en doğru şekilde göstermektedir.

Raporlama

Rapor Nesnesi : Rapor nesnesi IDataOnlyReportdan türetilen basit bir sınıftır. Örnek :

public class SertifikaReport : IDataOnlyReport
    {
        public SertifikaRequest Request { get; set; }

        public IEnumerable GetData()
        {
            var result = new List<Item>();

            using (var connection = SqlConnections.NewByKey("Default"))
            {
                new SertifikaRepository().List(connection, this.Request)
                    .Entities.ForEach(row =>
                    {
                        result.Add(new Item
                        {
                            FirmaAdi = row.FirmaFirmaAdi,
                            Kategori = row.KategoriKategoriAdi,
                            Marka = row.Marka,
                            Durum = row.Durum,
                            IlkBasvuruTarihi=row.IlkBasvuruTarihi,
                            YenilenmeTarihi=row.SonYenilemeTarihi,
                            BitisTarihi=row.SertifikaBitisTarihi
                        });
                    });
            }

            return result;
        }

        public List<ReportColumn> GetColumnList()
        {
            return ReportColumnConverter.ObjectTypeToList(typeof(Item));
        }

        [BasedOnRow(typeof(Gimnet.Sertifika.Entities.HelalSertifikaRow))]
        public class Item
        {
            public string FirmaAdi { get; set; }

            public string Kategori { get; set; }

            public string Marka { get; set; }

            public Sertifika.SertifikaDurumIds Durum { get; set; }

            public DateTime? IlkBasvuruTarihi { get; set; }

            public DateTime? YenilenmeTarihi { get; set; }

            public DateTime? BitisTarihi { get; set; }
        }
    }

Javascript Kütüphanelerini Serenity'ye Uyarlamak

Serenity SERENE şablonuyla birlikte bize en temel 2 tür ekran sunuyor: Dialog ve grid ekranları. Dialogta PropertyGrid sistemi üzerinde tablo alanları otomatik olarak diziliyor. Gridde ise slicgrid componenti ile listeleme işlevi sağlanıyor. Her iki ekranda da toolbar, pager gibi temel işlevler sunulmuş durumda. Bu standart ekranların dışında haliahzırda zaten kodlanmış bir sayfa formatını serenity ile birlikte kullanabilirsiniz. Ancak bunun için bu kütüphaneyi serenity'ye uyarlamak gerekiyor.

-Kütüphane javascript, intellisense desteklenmez -Serentiy C#, intellisense desteklenir, enum ve geenric tipler kullanılabilir, linq gibi işlevlerden yaralanılabilir.

Dönüştürmenin en temel hali: Kütüphaneeki bütün temel tipleri (sınıflar, enumlar, propertyler vs) C# tarafında boş olarak tanımlanır, ScriptImport klasörü altında maplenir.

Master-Detail Kayıtların Gösterilmesi

Uygulamlarda çoğunlukla karşımıza çıkan 1-n ilişkili Master-Detail kayıtlarının gösterilmesi/düzenlenmesi konusunu serenity oldukça basit bir şekilde ele alır. Bu tür kayıtlara örnek vermek gerekirse:

  1. Öğrenci-> Dersleri
  2. Sipariş -> Detayları
  3. Kitap -> Bölümleri

Bu şekilde bir ana kayıt ve ona bağlı birden çok detay kayıt olduğu durumlarda serenity'nin bize sunduğu temel entity düzenleme modeline ufak bir genişletme yapmamız gerekiyor. Öncelikle ana (master) kaydı düzenlediğimiz entity'nin XXXForm.cs dosyasında tıpkı diğer property alanları gibi bu detay kayıtlarını da eklemeliyiz. Aşağıdaki örnekte gördüğünüz gibi Order (sipariş) ekranında diğer kolonları ifade eden bir alan gibi DetailList koyduk ancak bu diğerlerinden farklı olarak birden çok kaydı ifade eden bir yapı arzediyor. Dialog ekranında bu burden çok kaydın yönetilmesini sağlamak için attribute olarak verdiğimiz OrderDetailsEditor sınıfı ile bu kayıtlaırn yönetilmesi gerektiğini belirtmiş olduk. Yani bu kolon diğerlerinden farklı olarak bu editör ile yönetilecek demiş olduk.

    [BasedOnRow(typeof(Entities.OrderRow))]
    public class OrderForm
    {
        //...
        public Int32? EmployeeID { get; set; }

        [Category("Order Details")]
        [OrderDetailsEditor]
        public List<Entities.OrderDetailRow> DetailList { get; set; }

        [Category("Shipping")]
        public DateTime ShippedDate { get; set; }
        //...
    }

Peki bu editör nasıl yazılır? Script projemizde ilgili editörün koduna baktığımızda GridEditorBase isimli sınıtan türeyen basit bir editör olduğunu anlayabiliriz. Bununla master kayda bağlı OrderDetailRow tipinde detay kayıtları yönetileceğini Form sınıfımız anlamış oluyor ve bize aşağıdaki gibi bir ekran sunuyor.

  [ColumnsKey("Northwind.OrderDetail"), DialogType(typeof(OrderDetailDialog)),   LocalTextPrefix("Northwind.OrderDetail")]
    public class OrderDetailsEditor : GridEditorBase<OrderDetailRow>
    {
        public OrderDetailsEditor(jQueryObject container)
            : base(container)
        {
        }

        protected override bool ValidateEntity(OrderDetailRow row, int? id)
        {
            row.ProductID = row.ProductID.ToInt32();

            var sameProduct = view.GetItems().FirstOrDefault(x => x.ProductID == row.ProductID);
            if (sameProduct != null && ID(sameProduct) != id)
            {
                Q.Alert("This product is already in order details!");
                return false;
            }

            row.ProductName = ProductRow.Lookup.ItemById[row.ProductID].ProductName;
            row.LineTotal = (row.Quantity ?? 0) * (row.UnitPrice ?? 0) - (decimal)(row.Discount ?? 0);

            return true;
        }
    }

1e N ilişkili kayıtlar için bir grid eklemiş olduk

Gridde Kolon Formatlama

Slickgridde bir kolonun formatlanması için aşağıdaki şekilde ISlickFormatter'den implemente edeceğiniz bir classı attribute olarak ilgili kolona attribute olarak vermeniz yeterlidir.

/*******Script projesi altında********/

public class FreightFormatter : ISlickFormatter
    {
        public string Format(SlickFormatterContext ctx)
        {
            return "<span class='freight-symbol'>" +
                Q.HtmlEncode(ctx.Value) +
                "</span>";
        }
    }

/*******Web projesi altında XXXColumn.cs dosyasında********/

public class OrderColumns
    {
        //....
        [FreightFormatter]
        public Decimal? Freight { get; set; }
    }

Liste Ekranlarına Filtre Eklemek

Grid ekranında aşağıdaki metodu override ederek her bir kolon için kolaylık filtre ekleyebilirsiniz.

protected override void CreateToolbarExtensions()
        {
            base.CreateToolbarExtensions();

            CustomerFilter = AddEqualityFilter<CustomerEditor>(Fields.CustomerID);

            AddEqualityFilter<EnumEditor>(Fields.ShippingState,
                options: new EnumEditorOptions { EnumKey = "Northwind.OrderShippingState" });

            AddEqualityFilter<LookupEditor>(Fields.ShipVia,
                options: new LookupEditorOptions { LookupKey = ShipperRow.LookupKey });

            AddEqualityFilter<LookupEditor>(Fields.ShipCountry,
                options: new LookupEditorOptions { LookupKey = "Northwind.OrderShipCountry" });

            AddEqualityFilter<OrderShipCityEditor>(Fields.ShipCity, init: w => w.CountryEditorID = Fields.ShipCountry);

            AddEqualityFilter<LookupEditor>(Fields.EmployeeID,
                options: new LookupEditorOptions { LookupKey = EmployeeRow.LookupKey });
        }

CustomerEditor bir field için standart bir editörden daha fazlasına ihtiyacımız olduğunda kullanacağımız formatı gösteriyor. Bunun için ayrı bir class tanımlayıp daha özelleştirmiş bir filtre editör oluşturabilmekteyiz.

Başka bir kullanım

protected override void CreateToolbarExtensions()
        {
            base.CreateToolbarExtensions();

            supplier = Widget.Create<LookupEditor>(
                    element: e => e.AppendTo(toolbar.Element)
                        .Attribute("placeholder", "--- " + Q.Text("Db.Northwind.Product.SupplierCompanyName") + " ---"),
                    options: new LookupEditorOptions { LookupKey = "Northwind.Supplier" });

            supplier.Change(e => Refresh());

            category = Widget.Create<LookupEditor>(
                    element: e => e.AppendTo(toolbar.Element)
                        .Attribute("placeholder", "--- " + Q.Text("Db.Northwind.Product.CategoryName") + " ---"),
                    options: new LookupEditorOptions { LookupKey = "Northwind.Category" });

            category.Change(e => Refresh());
        }

        protected override bool OnViewSubmit()
        {
            if (!base.OnViewSubmit())
                return false;

            var req = (ListRequest)view.Params;
            req.EqualityFilter = req.EqualityFilter ?? new JsDictionary<string, object>();
            req.EqualityFilter["SupplierID"] = supplier.Value.ConvertToId();
            req.EqualityFilter["CategoryID"] = category.Value.ConvertToId();
            return true;
        }

Özel filtre editör tanımlamak

Filtre editöründe combodaki elemanların metinsel gösterimi özelleştirdiğimiz bir CustomerEditor aşağıdaki gibidir.

public class CustomerEditor : LookupEditorBase<CustomerRow>
    {
        public CustomerEditor(jQueryObject container)
            : base(container)
        {
        }

        protected override string GetLookupKey()
        {
            return CustomerRow.LookupKey;
        }

        protected override string GetItemText(CustomerRow item, Lookup<CustomerRow> lookup)
        {
            return base.GetItemText(item, lookup) + " [" + item.CustomerID + "]";
        }
    }

GetItemText metodunu override ederek her satır metninin başına sonuna [...] şeklinde ekleme yapmış olduk.

Attribute Listesi

IdPropertyAttribute

Yerelleştirme-Çoklu Dil Desteği

Serenity dahili çoklu dil desteğine sahiptir. Farklı dil dosyaları

Web\Scripts\site\texts altında site.DİL_KODU.json şeklinde json dosyaları olarak tutulur.

Yazılımın her noktasındaki metinleri yerelleştirmek için şu hiyerarşik yapı kullanılır:

  • Formlardaki bir alanı yerelleştirmek için form tanımlarının başında bulunan [FormScript("Membership.ChangePassword")] şeklindeki tanımlayıcı kullanılır. Örnek:

"Forms.Membership.ChangePassword.OldPassword":"Mevcut Şifre",

  • Enumlardaki tip ve değerleri yerelleştirmek için EnumScript attribute'i ile
[EnumKey("Something")]
    public enum Sample
    {
        [Description("First Value")]
        Value1 = 1,
        [Description("Second Value")]
        Value2 = 2
    }

Örnek:

"Enums.Something.Value1":"Birinci Değer",

  • Navigasyon menüsünde bşlıkları güncellemek için

"Navigation.Administration": "Yönetim",

  • Yetki tanımları

"Permission.Administration:Security": "Yönetim: Kullanıcı, Rol Yönetimi ve Haklandırma",

  • Validasyon mesajları

"Validation.AuthenticationError": "Geçersiz kullanıcı adı ya da şifre!",