
Windows Azure Üzerinde OWIN
OWIN makale serisinde sizlere OWIN hakkındaki detayları vermeye çalışırken, Katana projesi yardımıyla da elimden geldiğince farklı örnekler paylaşmayı hedefledim. Daha önce Katana üzerinde, IIS üzerinde, Konsol ve Windows Forms uygulamalarımızda Katana yardımıyla sunduğumuz OWIN katmanları ve web uygulamalarımızı bu makalemde bulutta sunmayı planlıyorum. Hedefimiz Windows Azure üzerinde OWIN katmanlarımızı ve uygulamalarımızı sunmak…
Windows Azure üzerinde Katana yardımıyla OWIN desteği sunmak temelde bir Windows hizmeti üzerinde sunmaktan farklı değil aslında. Bu sebeple Azure Worker Role üzerinden gitmek doğru olacaktır.
Bu makaleyi okuyorsanız Windows Azure deneyiminizin olduğunu varsayıyorum; ama yine de hatırlatmakta fayda var, başlarken Windows Azure SDK’sının bilgisayarınızda kurulu olduğuna emin olun…

İşe öncelikle projemizi oluşturarak başlayalım;

Projemizi oluştururken bizi karşılayan New Windows Azure Cloud Service diyaloğunda listemize sadece Worker Role’ü ekliyoruz;

Proje açıldıktan sonra sıra geliyor NuGet paketlerini kurmaya;
Install-Package Microsoft.Owin.Hosting -Pre Install-Package Microsoft.Owin.Host.HttpListener -Pre Install-Package Microsoft.AspNet.WebApi.Owin -Pre Install-Package Microsoft.Owin.StaticFiles -Version 0.20-alpha-20220-88 -Pre
Listeden de anlayacağınız üzere Azure Worker Role’ümüzde statik dosyalar kadar ASP.Net Web API’yi de kullanmayı planlıyorum. Sunucu olarak da her zaman ki gibi HttpListener kullanacağım.
NuGet paketlerinin kurulması sonrası sırada servisimizin çıkış yapacağı uç nokta tanımlarının yapılmasında. Endpoint tanımlamaları sayesinde içerideki 80 (HTTP) portunu dış dünyaya yine 80 (HTTP) portuyla açacağız. Bu ayar için Enterprisecoding.AzureOWIN projesi Roles klasörü altında yer alan OWINRole’e gelerek bu rolün özelliklerini açalım.

Açılan pencereden Endpoints segmesine gelerek Add Endpoint butonu yardımıyla aşağıdaki özelliklerle yeni bir endpoint ekleyelim;
Name | OWINHizmeti |
Type | Input |
Protocol | http |
Public Port | 80 |
Private Port | 80 |
Bu basit ayarlardan sonra sıra geldi standart OWIN yapılandırma adımlarını takip ederek katmanlarımızı belirlemeye. Her zamanki gibi öncelikle OWINRole projemizde Startup sınıfımızı oluşturalım;
using System.Web.Http; using Owin; namespace OWINRole { internal class Startup { public void Configuration(IAppBuilder app) { var config = new HttpConfiguration(); config.Routes.MapHttpRoute( "Default", "api/{controller}/{id}", new {id = RouteParameter.Optional}); app.UseWebApi(config); } } }
Test için seçtiğim controller ise bir önceki makalemde kullandığım YapilacaklarController olacak;
using System.Collections.Generic; using System.Web.Http; namespace OWINController { public class YapilacaklarController : ApiController { public IEnumerable<Yapilacak> Get() { return new[] { new Yapilacak { Id = 1, Content = "OWIN makaleleri yaz", Tamamlandi = true }, new Yapilacak { Id = 2, Content = "Roslyn scripting örneği kodla", Tamamlandi = true }, new Yapilacak { Id = 3, Content = "Geri bildirimleri yanıtla", Tamamlandi = false }, new Yapilacak { Id = 4, Content = "Tatilin tadını çıkar", Tamamlandi = false } }; } } public class Yapilacak { public int Id; public string Content; public bool Tamamlandi; } }
Şimdi sıra geldi sunucumuzu başlatmaya. Bunun için WorkerRole sınıfımız içerisine WebApp örneğinin referansını tutmak için IDisposable türünden webApp değişkenini ekleyelim. Bunun nedeni, diğer projelerimizden farklı olarak bir Azure Worker Role’ün aynı Windows Hizmetlerinde olduğu gibi uygulamayı başlatan ve bitiren noktaların iki ayrı fonksiyon olmasıdır. bu sebeple tüm süreci tek bir using bloğu içine alamayız. OnStart ile hizmeti başlatan kodu yazarken, OnStop ile bu hizmeti sonlandırıp kullandığımız kaynakları da işletim sistemine geri iade etmeliyiz. Öncelikle hizmetimizi başlatalım;
public override bool OnStart() { // Set the maximum number of concurrent connections ServicePointManager.DefaultConnectionLimit = 12; // For information on handling configuration changes // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357. webApp = WebApp.Start<Startup<("[URL]"); return base.OnStart(); }
Dikkat ederseniz hizmetimizin açılacağı url’yi belirtmedim. Bunun nedeni derleme sırasında Azure Worker Role’ümüze Windows Azure tarafından hangi endpoint’in atanacağını bilmiyor olmam. Bu bilgileri yapılandırmadan RoleEnvironment yardımıyla aşağıdaki gibi çekebiliriz;
var ucNokta = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["OWINHizmeti"];
Son haliyle OnStart fonksiyonumuz aşağıdaki şekilde olacaktır;
public override bool OnStart() { // Set the maximum number of concurrent connections ServicePointManager.DefaultConnectionLimit = 12; var ucNokta = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["OWINHizmeti"]; webApp = WebApp.Start<Startup>(ucNokta.Protocol + "://" +ucNokta.IPEndpoint); return base.OnStart(); }
Hizmetimizin durdurulduğu OnStop fonksiyonu içerisinde de servisimize ait kaynakları işletim sistemine iade etmeliyiz;
webApp.Dispose(); webApp = null;
Son haliyle WorkerRole sınıfımız aşağıdaki şekilde olacaktır;
using System; using System.Diagnostics; using System.Net; using System.Threading; using Microsoft.Owin.Hosting; using Microsoft.WindowsAzure.ServiceRuntime; namespace OWINRole { public class WorkerRole : RoleEntryPoint { private IDisposable webApp; public override void Run() { // This is a sample worker implementation. Replace with your logic. Trace.WriteLine("OWINRole entry point called", "Information"); while (true) { Thread.Sleep(10000); Trace.WriteLine("Working", "Information"); } } public override bool OnStart() { // Set the maximum number of concurrent connections ServicePointManager.DefaultConnectionLimit = 12; var ucNokta = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["OWINHizmeti"]; webApp = WebApp.Start<Startup>(ucNokta.Protocol + "://" + ucNokta.IPEndpoint); return base.OnStart(); } public override void OnStop() { if (webApp != null) { webApp.Dispose(); webApp = null; } base.OnStop(); } } }
Şimdi sıra geldi statik içeriğimize.. Bunun için yine bir önceki makalemde kullandığım index.html’i kullanacağım;
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <head> <meta charset="utf-8" /> <title>Enterprisecoding - OWIN Arayüz Örneği</title> <script src="http://code.jquery.com/jquery-2.0.0.min.js"></script> </head> <style> /* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 License: none (public domain) */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; } article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block } body { line-height: 1 } ol, ul { list-style: none } blockquote, q { quotes: none } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } table { border-collapse: collapse; border-spacing: 0; } /* * Copyright (c) 2012 Thibaut Courouble * http://www.webinterfacelab.com * * Licensed under the MIT License: * http://www.opensource.org/licenses/mit-license.php ================================================== */ /* ======================================================== Page ===================================================== ================================================== */ body { background: #f8f6f6; color: #404040; font-family: 'Lucida Grande', Verdana, sans-serif; font-size: 13px; font-weight: normal; line-height: 20px; } a { color: #1e7ad3; text-decoration: none; } a:hover { text-decoration: underline } .container { margin: 50px auto; width: 380px; } /* ======================================================== Icons ===================================================== ================================================== */ [class^="icon-"], [class*=" icon-"] { display: inline-block; width: 12px; height: 12px; vertical-align: -2px; margin-right: 2px; background-image: url("http://demo.webinterfacelab.com/14-to-do-list/img/sprite.png"); background-repeat: no-repeat; text-indent: 100%; white-space: nowrap; overflow: hidden; } .icon-check { background-position: 0 0 } .icon-add { background-position: -12px 0 } .icon-delete { background-position: -24px 0 } .icon-settings { background-position: -36px 0 } .icon-previous { background-position: -48px 0 } .icon-next { background-position: -60px 0 } /* ======================================================== Todo List ===================================================== ================================================== */ .todo { position: relative; width: 260px; margin: 0 auto; padding: 12px 0; background: #fff; border: 1px solid; border-color: #dfdcdc #d9d6d6 #ccc; border-radius: 2px; -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } .todo:before, .todo:after { content: ''; position: absolute; z-index: -1; height: 4px; background: #fff; border: 1px solid #ccc; border-radius: 2px; } .todo:after { left: 0; right: 0; bottom: -3px; -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } .todo:before { left: 2px; right: 2px; bottom: -5px; border-color: #c4c4c4; -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); } .lt-ie9 .todo:before, .lt-ie9 .todo:after { height: 1px; border-top: 0; } .todo-list { border-top: 1px solid #e6ebed } .todo-list:before { content: ''; width: 3px; z-index: 2; border: 1px solid #f2e3df; border-width: 0 1px; position: absolute; top: 0px; bottom: 0px; left: 35px; } .todo-list li { position: relative; padding: 7px 15px 7px 50px; line-height: 21px; font-size: 12px; color: #8b8f97; border-bottom: 1px solid #e6ebed; } input[type=checkbox] { display: none; } input[type=checkbox]:checked + .toggle:after{ opacity: 1; } .todo-list .toggle { display: block; height: 35px; width: 35px; position: absolute; top: 0px; bottom: 0px; left: 0px; text-indent: 100%; overflow: hidden; cursor: pointer; } .toggle:after { content: ''; position: absolute; position: absolute; width: 7px; height: 3px; background: transparent; top: 14px; left: 13px; border: 2px solid #aaa; border-top: none; border-right: none; -webkit-transform: rotate(-45deg); -moz-transform: rotate(-45deg); -o-transform: rotate(-45deg); -ms-transform: rotate(-45deg); transform: rotate(-45deg); opacity: 0; } .todo-list .toggle:before { content: ''; width: 15px; height: 15px; background: #faf9f9; border: 1px solid #6bb3ca; border-radius: 2px; position: absolute; top: 9px; left: 9px; -webkit-box-shadow: 0 1px 1px #dfecf4; -moz-box-shadow: 0 1px 1px #dfecf4; box-shadow: 0 1px 1px #dfecf4; } .todo-list .toggle:hover:before { -webkit-box-shadow: 0 0 3px #6bb3ca; -moz-box-shadow: 0 0 3px #6bb3ca; box-shadow: 0 0 3px #6bb3ca; } .todo-list .done .toggle:before, .todo-list .toggle:active:before { border-color: #c0c0c0 #ccc #d8d8d8; -webkit-box-shadow: inset 0 1px rgba(0, 0, 0, 0.05), inset 0 5px 5px rgba(0, 0, 0, 0.05); -moz-box-shadow: inset 0 1px rgba(0, 0, 0, 0.05), inset 0 5px 5px rgba(0, 0, 0, 0.05); box-shadow: inset 0 1px rgba(0, 0, 0, 0.05), inset 0 5px 5px rgba(0, 0, 0, 0.05); } .todo-controls { margin: 0 15px 12px 50px; height: 12px; } .todo-controls li { float: left } .todo-controls li + li { margin-left: 10px } .todo-controls .right { float: right } .todo-controls a { display: block; margin: 0; opacity: .6; } .todo-controls a:hover { opacity: 1 } .todo-pagination { margin: 12px 12px 0 50px; height: 22px; } .todo-pagination li { float: left } .todo-pagination .next { float: right } .todo-pagination .next i { margin: 0 0 0 2px } .todo-pagination a, .todo-pagination span { display: block; line-height: 22px; font-size: 11px; color: #676f7f; } .todo-pagination a { padding: 0 8px; text-shadow: 0 1px #fff; background: #f1f0f0; border-radius: 3px; } .todo-pagination a:hover { background: #e9e9e9; text-decoration: none; } .todo-pagination span { padding: 0 4px; opacity: .3; } /* ======================================================== About ===================================================== ================================================== */ .about { margin: 80px auto 50px; padding: 15px 20px; width: 300px; text-align: center; color: #777; text-shadow: 0 1px rgba(255, 255, 255, 0.7); background: rgba(0, 0, 0, 0.05); border-radius: 3px; } .about a { padding: 1px 3px; margin: 0 -1px; color: #1c74c8; text-decoration: none; border-radius: 2px; } .about a:hover { color: #fff; text-shadow: 0 1px #0063A6; background: #0090D2; } .links { zoom: 1 } .links:before, .links:after { content: ""; display: table; } .links:after { clear: both } .links a { padding: 6px 0; float: left; width: 50%; font-size: 14px; } .author { margin-top: 15px; font-size: 11px; } </style> <body> <script type="text/javascript"> function YapilacaklariGetir() { $.ajax({ url: '/api/yapilacaklar', type: 'GET', dataType: 'json', success: function (data) { YapilacaklariYaz(data); }, error: function (x, y, z) { alert(x + '\n' + y + '\n' + z); } }); } function YapilacaklariYaz(yapilacaklar) { var sonuc = ""; $.each(yapilacaklar, function (index, yapilacak) { sonuc += yapilacak.Tamamlandi ? "<li class='done'><input type='checkbox' id='todo" + yapilacak.Id + "' checked disabled />" : "<li><input type='checkbox' id='todo" + yapilacak.Id + "'/>"; sonuc += "<label class='toggle for='todo" + yapilacak.Id + "'></label>" + yapilacak.Content + "</li>"; }); $(".todo-list").html(sonuc); } </script> <div class="container"> <section class="todo"> <ul class="todo-controls"> <li><a href="javascript:void(0);" class="icon-add">Add</a></li> <li><a href="javascript:void(0);" class="icon-delete">Delete</a></li> <li class="right"><a href="javascript:YapilacaklariGetir();" class="icon-settings">Settings</a></li> </ul> <ul class="todo-list"></ul> <ul class="todo-pagination"> <li class="previous"><span><i class="icon-previous"></i> Geri</span></li> <li class="next"><a href="javascript:void(0);">İleri <i class="icon-next"></i></a></li> </ul> </section> </div> </body> </html>
Dikkat ettiyseniz bu defa ajax isteğini yaptığım javascript kodunda Web API adresini tam vermek yerine ‘/api/yapilacaklar‘ şeklinde verdim. Bunun nedeni Worker Role’ün hizmet vereceği tam adresi bilmemem.. Son olarak da; önceden olduğu gibi bu dosyayı da her derlemede çıktı klasörüne kopyalanacak şekilde ayarlayalım.
Bu kadar… Sonrasında projemizi F5 ile başlatıp yerel emulator’de başlatarak web browser üzerinden sonucu görebilirsiniz…


Gördüğünüz gibi Katan yardımıyla OWIN katmanlarını Windows Azure üzerinde sunmak oldukça kolay. Tüm süreç Windows hizmetinde sunmakla aynı…
Makalemin devamında hazırladığımız Azure Worker Role’ü Windows Azure’a deploy etme adımlarını paylaşacağım. Bu konuda deneyim sahibi olanlar bu kısmı geçebilirler…
Azure Worker Role’ümüzü hazırlayıp test ettikten sonra sıra geldi bunu deploy etmeye. Eğer çoktan bir Bulut Hizmeti (Cloud Service) oluşturmadıysanız ilk adım yönetim portaline geçip bir tane oluşturmak olmalı. New –> Cloud Service –> Quick Create adımları ardından yeni bir bulut hizmeti oluşturma ekranı bizi karşılayacaktır;

Hızlıca bir hizmet adresi ve hizmetin sunulacağı bölgeyi seçtikten sonra Create Cloud Service seçeneği yardımıyla bulut hizmetimizi oluşturabiliriz.

Şimdi tekrar Visual Studio’ya, projemize geri dönerek Solution Explorer’dan Enterprisecoding.AzureOWIN’i seçerek sağ tıklama menüsünden Publish seçeneğini seçiyoruz. Bu seçenek karşımıza Windows Azure Publish diyaloğunu getirecektir;

Daha önceden bir yayınlama (publish) işlemi yaptıysanız subscription bölümü dolu gelecektir. İlk defa yapıyorsanız merak etmeyin Sign in to download credentials linkine tıklayıp yapılandırma dosyasını indirebilir ve Import butonu yardımıyla da bu yapılandırmayı alabilirsiniz. Sonraki adım Settings diyaloğu;

Son olarak özet ekranı bizi bekliyor…

Özet ekranında yer alan Publish butonu yayınlama sürecini başlatacaktır. Sürecin devamını Windows Azure Activity Log penceresinden izleyebilirsiniz;

Başarılı bir yayınlama sonrasında artık bir web browser üzerinden yayınladığımız adresi açarak sonucu görebiliriz;


Aşağıdaki ekran görüntüsünde yapılan ajax çağrısını da görebilmeniz mümkün;