Asp.Net HTTPModule ve HTTPHandler

ASP.NET iş akış süreci, gelen bütün isteklerin ASP.NET tarafından iş akışı üzerinde bulunan birimlere (module) iletilmesiyle başlar. Her birim gelen isteği aldıktan ve istek üzerindeki tüm yetkiyi elde ettikten sonra, en uygun hale gelinceye kadar istek üzerinde işlem yapar. İstek bütün HTTPModule yapılarından geçtikten sonra ilgili HTTPHandler’a iletilir. HTTPHandler istek üzerinde bazı işlemler yaptıktan sonra, isteğe verilen cevap ASP.NET iş akışında isteğin geldiği HTTPModule yapıları aracılığıyla iletilir.

ASP.NET Runtime tarafından gelen isteklerde birden fazla HTTPModule kullanılmasının sebebi, Web uygulamalarında kullanılan Cache (ara belleğe alma), Session (kullanıcı tarafından oluşturulan oturum), Authentication (kimlik doğrulama) nesnelerinin her biri için farklı bir HTTPModule kullanılıyor olmasıdır. HTTPHandler ise HTTPModule yapılarından gelen bilgileri kullanarak uygun html çıktısını üreterek aynı HTTPModule yapıları üzerinden ASP.NET Runtime’a iletilir.

Daha detaylı anlatım için URL;

http://bidb.itu.edu.tr/seyirdefteri/blog/2013/09/08/httpmodule-ve-httphandler

Custom Configuration Section Kullanımı

Zamanla ve kullanıcı kontrollü olarak değişebilecek olan özelliklerin configte saklanması tercih edilen hızlı bir yöntemdir.

Web.config dosyamızın içerisine, configSections elementi içerisinde sectionGroup ve section tanımlamalarını yapmamız yeterli olacaktır.

Örnek tanımlama;

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 
 <configSections>
     <sectionGroup name="serviceConnectionGroupRedis">
         <section name="Redis.ServerSettings" type="ConsoleApplication1.RedisDynamic.RedisServerSettings,ConsoleApplication1"/>
     </sectionGroup>
 </configSections>
 
 <serviceConnectionGroupRedis>
     <Redis.ServerSettings DefaultDb="0" ConnectionStringOrName="localhost:6379" PreferSlaveForRead="true" />
 </serviceConnectionGroupRedis>

 <startup>
 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
 </startup>
</configuration>

C# tarafında ise section type’ta belirlenen dosya yaratılır. Bu dosya içeriğine göre config ayarlarımız yüklenmektedir.

namespace ConsoleApplication1.RedisDynamic
{
   public interface IRedisServerSettings
   {
       bool PreferSlaveForRead { get; }
       string ConnectionStringOrName { get; }
       int DefaultDb { get; }
   }


   public class RedisServerSettings : ConfigurationSection, IRedisServerSettings
   {
       public static Lazy<IRedisServerSettings> Settings = new Lazy<IRedisServerSettings>(() => ConfigurationManager.GetSection("serviceConnectionGroupRedis/Redis.ServerSettings") as RedisServerSettings);

       [ConfigurationProperty("PreferSlaveForRead", IsRequired = false, DefaultValue = false)]
       public bool PreferSlaveForRead { get { return Convert.ToBoolean(this["PreferSlaveForRead"]); } }

       [ConfigurationProperty("ConnectionStringOrName", IsRequired = true)]
       public string ConnectionStringOrName { get { return this["ConnectionStringOrName"] as string; } }
  
       [ConfigurationProperty("DefaultDb", IsRequired = false, DefaultValue = 0)]
       public int DefaultDb { get { return Convert.ToInt32(this["DefaultDb"]); } }
   }
}

Daha sonra istenilen yerde config ayarları çekilebilmektedir.

var ayarlar = ConfigurationManager.GetSection("serviceConnectionGroupRedis/Redis.ServerSettings");
//veya statik olarak type göre tanımlama yaptıysak aşağıdaki şekildede ayarlarımızı alabiliriz.
var ayar = RedisServerSettings.Settings.Value;

Anonymous Tiplerin Propertysine Ulaşmak

C# 3.0 ile gelen Anonymous tiplerin propertylerine aşağıdaki gibi ulaşabiliriz.

var anonymousTypeObject = new { Name = "Lorem", SurName = "Impuls" };

PropertyDescriptorCollection props = TypeDescriptor.GetProperties(anonymousTypeObject);
foreach (PropertyDescriptor loopParam in props)
{
    var paramName = loopParam.Name;
    var paramValue = loopParam.GetValue(anonymousTypeObject);
}

Transaction Isolation Level lar nedir?

SQL Server’da verilerimizi güncellerken, diğer transaction ların verilere nasıl ulaşacağı, lock ların nasıl ve na kadar süre tutulacağı, verilerin nasıl okunacağı  “transaction isolation level” ile karar verilir.

Isolation için   eş zamanlı olarak veri güncellemesi yapan transactionların etkilerinden, diğer transactionları koruma yeteneğidir diyebiliriz.
Her transaction, transaction tamamlanana kadar, güncellen veriyi kilitler.  Veri okuma işlemlerinde, “transaction isolation levels”, diğer transaction lar tarafından yapılan değişikliklerin etkilerinden koruma seviyeleridir.

Daha düşük seviye bir isolation level seçilirse, aynı anda birçok kullanıcı veriyer ulaşabilir ancak okunan veriler tutarlı veya güncel olmayabilir.
Yüksek seviye bir isolation level, okunan verinin tutarlılığını arttırken, bir transaction’ın başka transaction ları kilitlemesine neden olabilir.
Veri tutarlılığının üç temel yan etkisi vardır.
dirty read :  Bir transaction tarafından bir veri güncellenirken verinin okunmasıdır ve veri güncel olmayabilir
nonrepeatable read: Bir başka transaction, aynı satırı birçok kez okur ve her seferinde başka bir veri okursa oluşur.
phantom read : Bir silme veya yeni kayıt ekleme işlemi sırasında, aynı anda okuma işlemi yapılıyorsa ve bu kayıtlar da okunan kayıt setinin içerisindeyse oluşur.  Bu kullanıcının kayıtları eklemediğini düşünmesine ve ikince kez kayıt eklemeye çalışmasına neden olabilir.

En üst seviye isolation “SERIALIZABLE” dır. Tüm transaction her zaman aynı datayı alırlar ancak çok kullanıcılı sistemlerde, bir kullanıcının yaptığı işlem, diğer kullanıcıların işlemlerini etkiler.

En alt seviye isolation ise “READ UNCOMMITTED” dır.  Diğer transactionlar tarafından güncellenmeye başlamış, ancak henüz commit işlemi tamamlanmamış veriler okunabilir.

READ UNCOMMITTED Başka transactionlardan tarafından güncellenmekte olan, ancak henüz güncelleme işlemleri tamamlanmamış veriler okunabilir.

READ COMMITTED  Başka transactionlar tarafından güncellenmekte olan veriler, güncelleme işlemi tamamlanana kadar okunamazlar.  Bu “dirty read” i engeller.  Veri, her bir transaction arasında başka bir transaction tarafından güncellenebilir. Bu seviyede nonrepeatable veya phantom read ler oluşabilir.
READ COMMITTED seviyesi, SQL Server’ın varsayılan isolation seviyesidir.

REPEATABLE READ Güncellenen ancak henüz commit edilmeyen veriler, commit edilene kadar okunamaz veya başka transaction lar tarafından güncellenemez.

SNAPSHOT Veri herhangi bir zamanda okunabilir.  Okuma işlemi başladığında verinin son hali ne ise, o veri okunur.  Okuma için başlayan transaction, sadece başlamadan önce commit edilmiş verileri farkeder. Transaction başladıktan sonra başka bir transaction veriyi güncellerse, bu güncellemeleri görmez.

SERIALIZABLE  Commit edilmemiş veri okunamaz veya güncellenemez. Bir transaction, okunan veri aralığına ait key ile, okuma işlemi tamamlanana kadar insert işlemi yapamaz.

Güvenlik açısından en iyi isolation level Serializable’dır . Performans bakımından en hızlı çalışanı Read Uncommitted’dır.

SQL serverda isolation seviyesi ,

SET TRANSACTION ISOLATION LEVEL
(
READ COMMITTED | READ UNCOMMITTED
| REPEATABLE READ | SERIALIZABLE
)
ifadeleri ile belirtilir.

Alıntıdır.

Export to Excell Helper

public static void GridExportToExcel(string fileName, object dataTable)
{
    var grid = new GridView();
    grid.DataSource = dataTable;
    grid.DataBind();

    HttpContext.Current.Response.ClearContent();
    HttpContext.Current.Response.AddHeader("content-disposition", string.Format("attachment; filename={0}-{1}.xls", fileName, DateTime.Now.ToShortDateString().Replace(".", "")));
    HttpContext.Current.Response.ContentType = "application/ms-excel";
    HttpContext.Current.Response.ContentEncoding = System.Text.Encoding.Unicode;
    HttpContext.Current.Response.BinaryWrite(System.Text.Encoding.Unicode.GetPreamble());

    StringWriter sw = new StringWriter();
    HtmlTextWriter htw = new HtmlTextWriter(sw);
    grid.RenderControl(htw);

    HttpContext.Current.Response.Write(sw.ToString());
    HttpContext.Current.Response.End();
}

Kullanımı işe ağaıdaki gibidir.

UsefulHelpers.GridExportToExcel("Bankalar", table);

Browser Cache için JS ve CSS File Versiyonlama

Sitemizdeki js,css gibi dosyalar üzerinde değişiklikler yaptığımızda sitemizi güncelleriz fakat bazen browser cachesinden eski file’ı kullanabilir. Ve durum sıkıntı yaratabilir. Bunun önüne geçmek için File Versioning kullanılmalıdır.  versiyonlama işlemine versiyon değerini kendimiz set edebiliriz v=1.0 gibi. fakat bunu her değişiklikte elle yazmak yerine otomatize etmek çok daha mantıklı. Yapmak istediğimiz şey değişiklik yapılan dosyanın son değişiklik zamanını alıp dosya sonuna version olarak eklemek. Tabi Saniyede 50.000 request aldığımızı düşünürsek, her gelen istekte gidip sitede kullanılan css & js dosyalarının son değiştirilme tarihini almaya kalkarsak, bir süre sonra serverlarımız bu yükü kaldıramayabilir. Bunun için ise Data cache kullanılabilir. Bu dosyayı 5dk kadar cachelemek server işlem yükünü azaltacaktır.

Öncelikle Interfacemizi ekliyoruz;

public interface IDefaultCacheManager
{
    object Get(string key);
    void Set(string key, object data, int cacheTime);
    bool IsSet(string key);
    void Invalidate(string key);
}

Cache işlemleri methodlarım

public class DefaultCacheManager : IDefaultCacheManager
{
    private ObjectCache Cache { get { return MemoryCache.Default; } }

    public object Get(string key)
    {
        return Cache[key];
    }

    public void Set(string key, object data, int cacheTime)
    {
        CacheItemPolicy policy = new CacheItemPolicy();
        policy.AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(cacheTime);
        Cache.Add(new CacheItem(key,data),policy);
    }

    public bool IsSet(string key)
    {
        return (Cache[key] != null);
    }

    public void Invalidate(string key)
    {
        Cache.Remove(key);
    }
}

Helper’ım

public static class HtmlHelpers
{
    public static IDefaultCacheManager Cache = new DefaultCacheManager();

    public static string FileVersioning(this HtmlHelper helper, string fileName)
    {
        string cacheKey = fileName;
        object cachedVersion = Cache.Get(cacheKey);
        if (cachedVersion != null)
        {
            return cachedVersion as string;
        }
        else
        {
            var serverFilePath = HttpContext.Current.Server.MapPath(fileName);
            var version = File.GetLastWriteTime(serverFilePath).ToString("yyyyMMddhhmmss");
            Cache.Set(cacheKey, string.Format("{0}?v={1}", fileName, version),5);
            return string.Format("{0}?v={1}", fileName,version);
        }  
    }
}

View içerisinde kullanımı

<script src="@Html.FileVersioning("/Content/jquery-1.8.3.js")"></script>

C# ile MSSQL’e binary resim kaydetme

Controller Kaydet;

Image img = Image.FromFile(Server.MapPath("dosyayolu"));
byte[] Image binaryimg = Utils.imageToByteArray(img);
dbekleresim.Image = binaryimg;
dbekleresim.SaveChanges();

Utils Methodumuz;
public static byte[] imageToByteArray(System.Drawing.Image imageIn)
{
    MemoryStream ms = new MemoryStream();
    imageIn.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
    ms.Dispose();
    imageIn.Dispose();
    return ms.ToArray();
}

Controller Göster;
public ActionResult Index()
 {
    ViewBag.CustomerImg = dbekleresim.Image;
    return View();
}
CSHTML;
@{
var base64 = Convert.ToBase64String(ViewBag.CustomerImg);
imgSrc = String.Format("data:image/gif;base64,{0}", base64);
}
<img src="@imgSrc">

Controllerdan PartialView’ı render ile çalıştırıp html text’ini alma

protected string RenderPartialViewToString(string viewName, object model)
{
    if (string.IsNullOrEmpty(viewName))
    viewName = ControllerContext.RouteData.GetRequiredString("action");
    ViewData.Model = model;
    using (StringWriter sw = new StringWriter())
    {
        ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
        ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
        viewResult.View.Render(viewContext, sw);
        return sw.GetStringBuilder().ToString();
    }
}

DbEntityValidationException ile Validasyon hatalarını yakalama

“Validation failed for one or more entities. See ‘EntityValidationErrors’ property for more details” hatası aldığınız zaman aklınıza ilk gelen şey veritabanınızda null olmayan bir alana null bir değer vermiş olabilirsiniz veya  20 karakterlik bir string alanımız vardır buna 20 karakterden fazla bir kelime girmişsek bu hata ile karşılaşacağız.

Bu hatayı aldığımız zaman bize hangi alan ile ilgili durum olduğunu söylemeyecektir. Bu kısmını try-catch bloğunda catch(Exception ex) yerine catch(DbEntityValidationException ex) yazarak ulaşabiliriz.

catch (DbEntityValidationException ex)
{
    foreach (var errs in ex.EntityValidationErrors)
    {
         Response.Write(string.Format("Entity türü \"{0}\" şu hatalara sahip \"{1}\" Geçerlilik hataları:", errs.Entry.Entity.GetType().Name, errs.Entry.State))
        foreach (var err in errs.ValidationErrors)
        {
            Response.Write(string.Format("- Özellik: \"{0}\", Hata: \"{1}\"", err.PropertyName, err.ErrorMessage));
            var propName = err.PropertyName;
            var errMess = err.ErrorMessage;
        }
    }
}

err.PropertyName alanında hatanın hangi alandan dolayı oluştuğunu size söyleyecektir.

JS Dosyalarında Localization Resource Textleri kullanma

Bilindiği gibi js dosyalarının içerisinden direk resource içerisindeki bir tanımlamaya  ulaşıp içerisindeki değerleri alınamıyor. Bunun için kullandığım yapı şu şekildedir. Öncelikle sayfa script dosyamızın yükleneceği sayfada(cshtml) üzerinde resource dosyasındaki değerleri okuyup global değişkenlerimde tutuyorum aşağıdaki gibi

<script type="text/javascript">
var resourcedangelendeger = "@Html.Raw(GeneralRess.GeneralStrings.s_enaz_bir_parametre_secmelisiniz)";
</script>

Tanımlamamdan daha sonra ise js file dosyasını ekliyorum.

<script src="@Url.Content("~/Scripts/customScript.js")" type="text/javascript"></script>

Script Dosya içerisinde ise global olarak tutulan değişkenimdeki değerleri okuyabiliyorum. Script dosya içerisinde alert ile resourcedaki değeri ekranda gösterelim.

$(document).ready(function () {
    alert(resourcedangelendeger);
});