Mvc’de Çoklu Resim Yükleme ve DropBox’a Yedekleme
Selamlar;
Bugün son zamanlarda karşıma bolca çıkan toplu resim yükleme ve Upload ettiğimiz resimleri Dropbox’a nasıl yedekleyeceğimiz konusu üzerine konuşacağız. Aslında dropbox haricinde seminerlerde de bu konuya yeterince deyinmiş idim. Bir de burada en yalın hali ile konuyu ele almaya çalışacağım. Upload işlemi için Jquery ve Jquery.Form haricinde hiçbir library kullanmadan örneğimize geçelim. Tabi DrobBox için de başka bir library kullanacağız.
Öncelikle Nugetteden aşağıdaki Jquery.Form package indirilir.
Index.cshtml’e öncelikle aşağıdaki 2 script dosyası eklenir.
1 2 |
<script src="~/Scripts/jquery-2.1.4.min.js"></script> <script src="~/Scripts/jquery.form.min.js"></script> |
Resimleri yüklemek için aşağıda görülen “Ajax.BeginForm()” methodu kullanılır. İlk parametre “Action” ve ikinci parametre “Controller” ismidir. 3. parametre “HttpMethod” tipini yani “Post”‘u belirtmektedir. Son parametre “enctype” tarayıcıdan sunucuya form verileri gönderilirken kullanılan kodlama türünü belirtir. “AntiForgeryToken()” veriyi post ederken güvenliği sağlar. Url’e dinamik bir token ekler. Daha sonra yine server side tarafında ilgili token kontrol edilir.”<input type=’file’>” nesnesinde “multiple=’true'” özelliği çoklu dosya seçmeye yarar.
1 2 3 4 5 6 |
@using (Ajax.BeginForm("UploadImage", "Home", new AjaxOptions() { HttpMethod = "POST" }, new { enctype = "multipart/data-form" })) { @Html.AntiForgeryToken() <input type="file" name="files" multiple="true" /> <input type="submit" value="Yükle" /> } |
Resimlerin yüklenme sırasında yukarıda görülen “Loading” gif’inin gözükmesi için aşağıdaki kod konur. Görüldüğü gibi enbaşta gizlenmiştir.
1 |
<img src="~/Content/loader.gif" id="loader" style="display:none" /> |
Aşağıdaki script ile form post olduğu zaman “loader” gifinin gözükmesi ve işlem bittiği zaman tekrar gizlenmesi “ajaxForm”‘un “beforeSend” ve “complete” eventleri ile yapılmıştır.
1 2 3 4 5 6 7 8 |
$('form').ajaxForm({ beforeSend: function () { $("#loader").show(); }, complete: function (ImageList) { $("#loader").hide(); } }); |
HomeController.cs: Aşağıdaki kodlar resimler seçildikten sonra post edilen “UploadImage()”‘e aittir. “HttpPostedFileBase” türünde “IEnumerable<>” şeklinde dosya tipi beklemektedir. İlgili post edilen resimler local’de “Upload” klasörüne kaydedilmektedir. Aynı isimde 2 resimin, kaydedilmemesi için resimin fileName’inden uzantısı çıkarılıp, yeni oluşturulan Guid() “-” işareti ile birlikte bu ismin sonuna eklenmiş ve daha sonra uzantısı sona tekrar konarak ilgili resim kaydedilmiştir. Sonda “Thread.Sleeep” ile sistem 2sn bekletilerek yükleme logosunu ekranda daha uzun süre gözükmesi sağlanmıştı:) Ayrıca Action’ın başındaki [HttpPost] sadece post request’i ile erişilmesini sağlamıştır. Son olarak [ValidateAntiForgeryToken] ile Action’a gelen request’de “Token” kontrolü yapmakta ve böylece güvenlik büyük oranda arttırılmaktadır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
[HttpPost] [ValidateAntiForgeryToken] public JsonResult UploadImage(IEnumerable<HttpPostedFileBase> files) { if (files != null) { foreach (var file in files) { if (file != null && file.ContentLength > 0) { var routhPath = Server.MapPath("~/Upload"); string fileName = Path.GetFileNameWithoutExtension(file.FileName); fileName += "_" + Guid.NewGuid().ToString("N"); fileName += Path.GetExtension(file.FileName); var path = Path.Combine(routhPath, fileName); file.SaveAs(path); System.Threading.Thread.Sleep(2000); } } } return Json(null); } |
Yukarıdaki örnekde görüldüğü gibi Action “JsonResult” sonuç döndürmektedir. Şu an için “Null” değer dönülmüştür. Aşağıdaki örnekte öncelikle “MyViewModel” adında bir View Model yaratılmıştır. “Upload” klasörünün altındaki tüm dosyalar, eklenen Guid’den önceki isim’i ilgili MyViewModel’deki “Name” özelliğine atanmıştır. Aynı zamanda MyViewModel’deki “ImageUrl” alanına ilgili dosya url’i, olduğu gibi atanmıştır. “List<MyViewModel> imageList” nesnesine herbir oluşturulan item eklenmiş ve sonunda “Upload” klasöründeki tüm dosyalar ilgili List’e eklendikten sonra result, Json(imageList) şeklinde döndürülmüştür.
HomeController.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
using Microsoft.AspNet.SignalR; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Web; using System.Web.Mvc; using System.Threading.Tasks; namespace SeminerUpload4.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } [HttpPost] [ValidateAntiForgeryToken] public JsonResult UploadImage(IEnumerable<HttpPostedFileBase> files) { if (files != null) { foreach (var file in files) { if (file != null && file.ContentLength > 0) { var routhPath = Server.MapPath("~/Upload"); string fileName = Path.GetFileNameWithoutExtension(file.FileName); fileName += "_" + Guid.NewGuid().ToString("N"); fileName += Path.GetExtension(file.FileName); var path = Path.Combine(routhPath, fileName); file.SaveAs(path); System.Threading.Thread.Sleep(2000); } } } //Klasördeki tüm resimler gezilerek MyViewModel doldurulup Json olarak döndürülür. List<MyViewModel> imageList = new List<MyViewModel>(); string routhPath2 = Server.MapPath("~/Upload"); var images = Directory.EnumerateFiles(routhPath2).Select(fl => "/Upload/" + Path.GetFileName(fl)).ToList(); foreach (string url in images) { MyViewModel model = new MyViewModel(); model.ImageUrl = url; model.Name = Path.GetFileNameWithoutExtension(url).Split('_')[0]; imageList.Add(model); } return Json(imageList); } public class MyViewModel { public string ImageUrl { get; set; } public string Name { get; set; } } } } |
Index.cshtml(Full): Aşağıda makalenin başında yazdığımız Index.cshtml’i biraz daha geliştirdik. Görüldüğü gibi complete eventinde geriye bir Json tipinde ImageList dönülmektedir. Ilgili listenin “responseText”‘i Json’a Pars edilip, ilgili object içinde herbir item tek tek gezilerek string bir Html Table oluşturulur. Aşağıdaki kod’da dikkat edimesi gereken kısım her 3 kayıtta bir yeni satırın tablo içinde oluşturulmasıdır. İlgili tablonun içine upload edilen “<img>” nesneleri herbir kolonun yani “<td>” elementi içine konur. Daha sonra oluşturulan Html text, form içindeki “imageList” div’in html’ine basılır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
@{ ViewBag.Title = "Home Page"; } <script src="~/Scripts/jquery-2.1.4.min.js"></script> <script src="~/Scripts/jquery.form.min.js"></script> <script> $('form').ajaxForm({ beforeSend: function () { $("#loader").show(); }, complete: function (ImageList) { var obj = jQuery.parseJSON(ImageList.responseText); $("#loader").hide(); var html = "<table><tr>"; var row = 0; $.each(obj, function (key, value) { listItem = "<td>"; listItem += "<img src='" + value.ImageUrl + "' style='width:200px;height:200px'/></td>"; row += 1; if (row == 3) { row = 0; listItem += "</td></tr><tr>"; } else { listItem += "</td>" } html += listItem; }); html += "</tr></table>"; var list = $('#imageList'); list.html(html); } }); </script> <div class="jumbotron"> <h1>Çoklu Resim Yükleme</h1> </div> <img src="~/Content/loader.gif" id="loader" style="display:none" /> @using (Ajax.BeginForm("UploadImage", "Home", new AjaxOptions() { HttpMethod = "POST" }, new { enctype = "multipart/data-form" })) { @Html.AntiForgeryToken() <input type="file" name="files" multiple="true" /> <input type="submit" value="Yükle" /> } <div id="imageList"></div> |
Şimdi sıra geldi yüklenecek resimleri DropBox’a yedeklemeye:
Bunun için öncelikle https://www.dropbox.com/developers adresine gidilir.
1-)Buradan aşağıdaki gibi sol taraftaki “App Console” seçilir.
2-)Sağdaki button’una basılır.
3-) Aşağıda görüldüğü gibi öncelikle “Dropbox API app” seçilir. Daha sonra dropbox’daki tüm klasörlere erişmek için “My app needs access to files already on Dropbox” seçilir. Son olarak tüm dosya tiplerine erişmek için “All files types” seçilir. Ve yaratılacak application’a bir isim verilip “Creat app” buttonuna basılır.
4-)Uygulama yaratıldığında aşağıdaki gibi bir ekran ile karşılaşılır. “App key” ve “App secret”. Bizim için uygulamada kullanılacak önemli parametrelerdir. Ayrıca ilgili izin verildikten sonra dönüş yapılacak Url yani “Redirect URIs” aşağıdaki gibi “https://localhost:44300/” olarak belirtilmiştir.
Şimdi sıra geldi projemize bu yeni yarattığımız “UploadImages” uygulamamızı implemente etmeye.
1-) Bunun için aşağıda görüldüğü gibi “Spring.Social.Dropbox” paketi Nuget’den indirilir.
2-) DropBox ekranında hatırlarsanız “RedirectURIs” “https” ile başlıyordu. Bunun için proje tıklanıp, property ekranından SSL Enabled’a True değeri aşağıdaki gibi atanır.
3-) Daha sonra aşağıda görülen SSL URL kopyalanır ve Proje sağ tıklanıp Properties seçilir. Solda Web sekmesi seçildikten sonra Project Url kısmına kopyalanan bu SSL adresi yapıştırılır ve kaydedilir.
4-) Namespace’a aşağıdaki kütüpahaneler eklenir.
HomeController.cs:
1 2 3 4 |
using Spring.Social.Dropbox.Api; using Spring.Social.Dropbox.Connect; using Spring.Social.OAuth1; using Spring.IO; |
5-) HomeController.cs’e aşağıdaki “LoginDrop()” methodu eklenir. Öncelikle “DrobboxServiceProvider”‘a DropBox sayfasından aldığımız “App key” ve “App secret” parametre olarak atanır. Ayrıca bir de Full erişim yetkisi olarak “AccessLevel.Full” verilir. Daha sonra yaratılan nesne session’a atılır. Ayrıca “Token” almak için request’de kullanacağımız “IOAuth1Operations” yaratılır ve session’a atılır. “OAuthToken”‘da yaratılan “oauthOperations”‘ın “FetchRequestTokenAsync()” methodu ile asenkron olarak çekilir. Daha sonra ilgili app’in bu projede kullanılabilmesi için izin alınacak “DropBox” sayfasının url’i parametre olarak”CallbackUrl” dediğimiz geri dönülecek sayfanın url’i ve bu sayfanın lokasyonu parametre olarak belirlenir. Ve daha sonra “oauthOperations.BuildAuthorizeUrl()” methodu ile ilgili url çekilir. Tabi ilgili izinin DropBox’dan alınabilmesi için çekilen bu url’e “Redirect” ile yönlenilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public async Task LoginDrop() { DropboxServiceProvider serviceProvider = new DropboxServiceProvider("aec3xxxxxxt8h6", "aimbxxxxxxdr4y", AccessLevel.Full); Session["serviceProvider"] = serviceProvider; IOAuth1Operations oauthOperations = serviceProvider.OAuthOperations; Session["oauthOperations"] = oauthOperations; Spring.Social.OAuth1.OAuthToken requestToken = await oauthOperations.FetchRequestTokenAsync(null, null); Session["requestToken"] = requestToken; OAuth1Parameters parameters = new OAuth1Parameters(); parameters.CallbackUrl = "https://localhost:44300"; parameters.Add("locale", "tr-TR"); // izin verilecek websitesinin yeri belirlenir. string authorizeUrl = oauthOperations.BuildAuthorizeUrl(requestToken.Value, parameters); Response.Redirect(authorizeUrl); } |
6-) Aşağıda Dropbox’dan web sayfasının yaratılan “UploadImages” application’ını kullanması için izin istendiği ekran görünüyor. “Allow” button’una basılarak ilgili izin verilmiş olunur.
7-) İzin verildikten sonra “https://localhost:44300” yani HomeController/Index.cshtml sayfasına geri dönülür. Böylece Dropbox’a bağlanılmış olunur.
8-) Aşağıda UploadImage() methodu asenkron olarak değiştirilmiştir. İlgili resimler yüklendikten sonra dropbox’a da atılması için bir “IDropbox” nesnesine ihtiyaç vardır. Öncelikle DropBox RedirectUrl’den dönen”accessToken”, “IOAuth1Operations.ExchangeForAccessTokenAsync()” methodu ile asenkron olarak çekilir. Daha sonra session’daki “DropboxServiceProvider.GetApi()” methoduna az önce çekilen “accessToken”‘ın “Value” ve “Secret”‘ değerleri parametre olarak geçilerek, “IDropbox” nesnesi oluşturulur. Artık bu nesne ile Dropbox için birçok işlemi gerçekleştirebiliriz. Bu nesneyi session’a almamızın nedeni ise session süresi boyunca tekrardan Dropbox sayfasından izin alınmasını engellemektir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
[HttpPost] [ValidateAntiForgeryToken] public async Task<JsonResult> UploadImage(IEnumerable<HttpPostedFileBase> files) { if (files != null) { List<Task> Tasks = new List<Task>(files.Count()); IDropbox dropboxApi; //DropBox // İlgili provider'dan yani DropBox'dan geri dönüldükten sonra: if (Session["dropboxApi"] == null) { var accessToken = await ((IOAuth1Operations)Session["oauthOperations"]).ExchangeForAccessTokenAsync(new AuthorizedRequestToken((Spring.Social.OAuth1.OAuthToken)Session["requestToken"], null), null); dropboxApi = ((DropboxServiceProvider)Session["serviceProvider"]).GetApi(accessToken.Value, accessToken.Secret); Session["dropboxApi"] = dropboxApi; } else { dropboxApi = (IDropbox)Session["dropboxApi"]; } } } |
9-) Örneğin “dropboxApi” nesnesi ile yapılabilecek işlerden biri Dropbox’da belirlenen bir klasördeki dosyalar “Task<Entry>” türünde “dropboxApi.GetMetadataAsync(“UploadImages”)” ile asenkron olarak alınabilmesidir. Aşağıda “UploadImages” altındaki resimlerin path’leri “Debug=>Windows=>Output” altına yazdırılmıştır.
1 2 3 4 5 6 |
//DropBox'daki Resmilerin Path'i var List = await dropboxApi.GetMetadataAsync("UploadImages"); foreach (Entry file in List.Contents) { System.Diagnostics.Debug.WriteLine(file.Path); } |
Yine istenir ise Kullanıcı bilgileri “dropboxApi” ile aşağıdaki gibi çekilebilir.
1 |
var me = await dropboxApi.GetUserProfileAsync(); |
10-) Tüm atılacak dosyalar tek tek gezilerek ilgili proje altındaki Upload klasörüne kaydedilir. Aynı zamanda ilgili resim, Dropbox altında “UploadImages” klasörüne “dropboxApi.UploadFileAsync()” methodu ile asenkron olarak yüklenerek aşağıdaki gibi yedeklenir.
1 |
Tasks.Add(dropboxApi.UploadFileAsync(new FileResource(path), "/UploadImages/" + fileName)); |
HomeController.cs(Full):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
using Microsoft.AspNet.SignalR; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Web; using System.Web.Mvc; using System.Threading.Tasks; using Spring.Social.Dropbox.Api; using Spring.Social.Dropbox.Connect; using Spring.Social.OAuth1; using Spring.IO; namespace SeminerUpload4.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } public async Task LoginDrop() { DropboxServiceProvider serviceProvider = new DropboxServiceProvider("aec3judvp27t8h6", "aimbgb0qvf5dr4y", AccessLevel.Full); Session["serviceProvider"] = serviceProvider; IOAuth1Operations oauthOperations = serviceProvider.OAuthOperations; Session["oauthOperations"] = oauthOperations; Spring.Social.OAuth1.OAuthToken requestToken = await oauthOperations.FetchRequestTokenAsync(null, null); Session["requestToken"] = requestToken; OAuth1Parameters parameters = new OAuth1Parameters(); parameters.CallbackUrl = "https://localhost:44300?result=true"; parameters.Add("locale", "tr-TR"); // izin verilecek websitesinin yeri belirlenir. string authorizeUrl = oauthOperations.BuildAuthorizeUrl(requestToken.Value, parameters); Response.Redirect(authorizeUrl); } [HttpPost] [ValidateAntiForgeryToken] public async Task<JsonResult> UploadImage(IEnumerable<HttpPostedFileBase> files) { if (files != null) { List<Task> Tasks = new List<Task>(files.Count()); IDropbox dropboxApi; //DropBox // İlgili provider'dan yani DropBox'dan geri dönüldükten sonra: if (Session["dropboxApi"] == null) { var accessToken = await ((IOAuth1Operations)Session["oauthOperations"]).ExchangeForAccessTokenAsync(new AuthorizedRequestToken((Spring.Social.OAuth1.OAuthToken)Session["requestToken"], null), null); dropboxApi = ((DropboxServiceProvider)Session["serviceProvider"]).GetApi(accessToken.Value, accessToken.Secret); Session["dropboxApi"] = dropboxApi; } else { dropboxApi = (IDropbox)Session["dropboxApi"]; } //DropBox'daki Resimlerin Path'i var List = await dropboxApi.GetMetadataAsync("UploadImages"); foreach (Entry file in List.Contents) { System.Diagnostics.Debug.WriteLine(file.Path); } //----------------------------------------------- foreach (var file in files) { if (file != null && file.ContentLength > 0) { var routhPath = Server.MapPath("~/Upload"); string fileName = Path.GetFileNameWithoutExtension(file.FileName); fileName += "_" + Guid.NewGuid().ToString("N"); fileName += Path.GetExtension(file.FileName); var path = Path.Combine(routhPath, fileName); file.SaveAs(path); Tasks.Add(dropboxApi.UploadFileAsync(new FileResource(path), "/UploadImages/" + fileName)); //var result = await dropboxApi.UploadFileAsync(new FileResource(path), "/UploadImages/"+ fileName); //------------------Drop Box } } Task.WaitAll(Tasks.ToArray()); System.Threading.Thread.Sleep(2000); } List<MyViewModel> imageList = new List<MyViewModel>(); string routhPath2 = Server.MapPath("~/Upload"); var images = Directory.EnumerateFiles(routhPath2).Select(fl => "/Upload/" + Path.GetFileName(fl)).ToList(); foreach (string url in images) { MyViewModel model = new MyViewModel(); model.ImageUrl = url; model.Name = Path.GetFileNameWithoutExtension(url).Split('_')[0]; imageList.Add(model); } return Json(imageList); } public class MyViewModel { public string ImageUrl { get; set; } public string Name { get; set; } } } } |
Yukarıdaki örnekde proje içindeki bir folder’a çoklu olarak seçtiğimiz resimleri Ajax.Post ile upload ederken aynı zamanda Dropbox’da yedekledik. İş hayatında böyle bir seneryo ile karşılaşmanız pek de mümkün değildir. Bu makalede amaç Mvc ile toplu resim yüklemeyi ve Dropbox ile etkileşimi göstermektir. Aslında yapılması gereken, seçilen tüm resimleri ya bir Image Cdn’e ya da cloude’a örneğin Azure Blob Stroge’a konmasıdır. Eğer local folder’dan 1 milyon resim için request çekilse idi sistemin patlamaması işten bile değildi. Bunun çözümü için çekilen resimlerin cache’e alınması ve istendiğinde buradan verilmesi daha doğrudur. Ayrıca resimlerin, code ile oluşturulan bir html string olarak basılması yerine, javascript framworklerinden AngularJS veya KnockoutJS gibi kütüpahanelerin kullanılıp ilgili view modelin doldurulması ve bu modeli kullanan elementlerin, yüklenen resimleri göstermesi tercih edilmelidir. Bir başka çözüm de bunun bir Mvc projesi olmasından dolayı UploadImage() methodunun dönüş tipinin partial view olarak belirlenmesidir.
Geldik bir makalenin daha sonuna. Yeni bir makalede görüşmek üzere hoşçakalın.
Source: http://www.covalent.net/social-dropbox/refdoc/api.html
Makaleye ilk baktığımda .NET’te Dropbox API’sini kullanan paket mi vardı diye düşündüm ilk başta fakat kodları inceleyince Java Spring’in social.dropbox paketinin kullandığını gördüm yani Java Spring framework’u .NET ‘e portlamışlar. Spring ‘in .NET’de kullanım örneği vermesi açısından gayet iyi bir makale.
Teşekkürler Ömer;
Beyenmene sevindim:)
Hoşçakal…
Merhaba, Tüm adımları uyguladım. Fakat izin için gerekli işlem yapılamıyor. Dolayısyla dropbox’a ulaşamıyor. Sebebi ne olabilir?
Selamlar,
Sayfanızın https olması gerekiyor.(SSL verildiğine emin olun) Erişilecek klasörün ayrıca dropbox’dan yetkilendirilmesi gerekiyor. Ayrıca dropbox’a erişecek sayfa url’inin portu ile düzgün
varilmiş olması gerekmektedir.
İyi çalışmalar.
Hocam yazınız çok güzel teşekkürler emeğinize sağlık. Ben bir uygulama üzerinde resim yükleme işlemi yaptığım sırada eğer internet hızınız yavaş ise uygulama token hatası veriyor ve resim yüklenmiyor. Web config ayarlarından boyut ve süre ayarlaması yaptım ama bir sonuç alamadım. Ne yapabilirim acaba?
Selamlar Arda,
Makale yazılalı yaklaşık 1 yıl olmuş. O zaman böyle bir hata hiç almadım. Bu tarz third party firmalar kodlarını çok hızlı ve geriye dönük destek vermeden değiştirebiliyorlar. Bir de aynı işlemi başka bir networkden deneyebilirmisin? Belkide internete girdiğin ağa üzerinde veya makinada bir güvenlik duvarına takılıyorsundur.
İyi çalışmalar.
Hocam sistemi uzun uzun inceledim ve request süresi uzun olduğu zaman request timeout oluyor. Web.config üzerinde ayarlamalar yaptım ama bir sonuç alamadım.