ekrem özer

her yerde olan şeyler.

.Net Core MVC Ckeditor ve elFinder Kurulumu

Merhaba arkadaşlar bu makalemizde gelişmiş bir text editör olan ckeditor ve dosya yöneticisi olan elFinder'ı .net core mvc projemizde kurulumunu ve elfinder'i ckeditöre nasıl entegre edeceğimizi anlatacağım. ckeditör ile .net projelerimizde ckfinder kullanırdık ancak .net core için ckfinder geliştirilmedi. Bende bu arayışla tesadüfen ckfinder alternatifi hatta ondan çok daha gelişmiş olduğunu düşündüğüm elfinder'ı buldum.

İlk olarak statik dosyalarım için projeme Assets klasörü açıyorum ve içine Images, Js ve Plugins adında üç tane daha klasör açıyorum. Klasör yapım aşağıdaki gibi oluyor,

Statik dosyları projemde kullanabilmek için startup'da assets klasörümü tanımlıyorum, siz burada wwwroot klasörünü de kullanabilirsiniz.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
	app.UseStaticFiles(new StaticFileOptions
	{
		FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "Assets")),
		RequestPath = "/Assets"
	});
}

Şimdi ckeditor 4 ile elfinder dosyalarını indirip Plugins klasörüme atıyorum. (İndirme, döküman ve kaynak kodu linklerini makalenin sonunda paylaşacağım.) Elfinderin projemde kullanabilmek için nuget paketini yüklemem gerekiyor. Nuget manager'dan aşağıdaki paketi yüklüyorum.

elFinder.NetCore

Nugetı yükledikten sonra projeme ElFinderHelper adında bir class ekliyorum.

namespace CoreMvcCkEditorElFinder.Web.Helper
{
    public static class ElFinderHelper
    {
        public static string RootPath = "Assets\\Images";
        public static string RootUrlPath = "Assets/Images";
        public static string ThumbUrl = "/elfinder/thumb/";

        public static Connector GetConnector(HttpRequest request)
        {
            var driver = new FileSystemDriver();

            var absoluteUrl = UriHelper.BuildAbsolute(request.Scheme, request.Host);
            var uri = new Uri(absoluteUrl);

            var appRoot = Directory.GetCurrentDirectory();
            var rootDirectory = Path.Combine(appRoot, RootPath);

            var url = $"{uri.Scheme}://{uri.Authority}/{RootUrlPath}/";
            var urlThumb = $"{uri.Scheme}://{uri.Authority}{ThumbUrl}";

            var root = new RootVolume(rootDirectory, url, urlThumb)
            {
                IsReadOnly = false, //Bu alan true olursa dosyalar sadece okunabilir olur
                IsLocked = true, // Bu alan true ise dosyalar ve klasörler silinemez, yeniden adlandırılamaz veya taşınamaz
                Alias = "Dosyalarım", //Elfinder penceresinde görünen klasör adı
                //MaxUploadSizeInKb = 2048, //Kullanıcı tarafından yüklenen dosyaya uygulanan sınır <= 2048 KB
                //LockedFolders = new List<string>(new string[] { "Folder1" } //Kilitlenecek klasörlerin isimleri
                ThumbnailSize = 100
            };

            driver.AddRoot(root);

            return new Connector(driver)
            {
                MimeDetect = MimeDetectOption.Internal
            };
        }
    }
}

Bu classımda GetConnector adında bir metod var ön yüzden gelen istekleri bu connector aracığılı ile işliyor olacağız. Root nesnesinin aldığı paremetlerin ne işe yaradığını bulabildiğim kadarıyla yanlarına açıklama satırı olarak ekledim. Şimdi projeme ElFinderController adında bir controller ekliyorum.

namespace CoreMvcCkEditorElFinder.Web.Controllers
{
    public class ElFinderController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        public IActionResult FileManager()
        {
            return View();
        }

        public async Task<IActionResult> Connector()
        {
            var connector = ElFinderHelper.GetConnector(Request);
            return await connector.ProcessAsync(Request);
        }

        public async Task<IActionResult> Thumbs(string hash)
        {
            var connector = ElFinderHelper.GetConnector(Request);
            return await connector.GetThumbnailAsync(HttpContext.Request, HttpContext.Response, hash);
        }
    }
}

Controllerimin routin yapılandırmasını startup'a ekliyorum. Aslında thumb dışındakiler default olduğu için eklememize de gerek yok ancak siz değiştirmek istersiniz diye ekliyorum.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
	app.UseRouting();
	
	app.UseEndpoints(endpoints =>
	{
		endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
		endpoints.MapControllerRoute("ElFinderForCkEditor", "/elfinder", new { controller = "ElFinder", action = "Index" });
		endpoints.MapControllerRoute("ElFinderFileManager", "/elfinder/file-manager", new { controller = "ElFinder", action = "FileManager" });
		endpoints.MapControllerRoute("ElFinderConnector", "/elfinder/connector", new { controller = "ElFinder", action = "Connector" });
		endpoints.MapControllerRoute("ElFinderThumbnail", "/elfinder/thumb/{hash}", new { controller = "ElFinder", action = "Thumbs" });
	});
}

Sonrada bu controllera dört adet Action ekliyorum;

Index: Ckeditör için kullanacağım view'ı dönecek.

FileManager: Bağımsız olarak elfinder'ı bir dosya yöneticisi gibi kullanmamızı sağlayacak view'ı dönecek.

Connector: Ön yüzü backend ile bağlayan connector'ü dönecek.

Thumbs: Dosya yöneticisinde gösterilecek küçük resimleri dönencek.

Controller'ı bitirdik, şimdi Index view'ımızı ekleyelim.

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
    <title>elFinder</title>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />

    <link rel="stylesheet" href="~/Assets/Plugins/elfinder/css/elfinder.full.css" />
    <link rel="stylesheet" href="~/Assets/Plugins/elfinder/css/theme.min.css" />
    <link rel="stylesheet" href="~/Assets/Plugins/elfinder/themes/elfinder-material-theme/Material/css/theme-gray.css" />

    <script src="~/Assets/Js/jquery.min.js"></script>
    <script src="~/Assets/Js/jquery-ui.min.js"></script>
    <script src="~/Assets/Plugins/elfinder/js/elfinder.min.js"></script>
</head>

<body>
    <div id="elfinder"></div>

    <script>
        $(document).ready(function () {

            var elFinderCommands = [
                'archive', 'back', 'chmod', 'colwidth', 'copy', 'cut', 'download', 'duplicate', 'edit', 'extract',
                'forward', 'fullscreen', 'getfile', 'help', 'home', 'info', 'mkdir', 'mkfile', 'netmount', 'netunmount',
                'open', 'opendir', 'paste', 'places', 'quicklook', 'reload', 'rename', 'resize', 'restore', 'rm',
                'search', 'sort', 'up', 'upload', 'view', 'zipdl'
            ];
           
            var disabledCommands = ['archive', 'callback', 'chmod', 'editor', 'netmount', 'ping', 'search', 'zipdl', 'help'];

            $.each(disabledCommands, function (i, cmd) {
                var commandIndex = $.inArray(cmd, elFinderCommands);
                commandIndex !== -1 && elFinderCommands.splice(commandIndex, 1);
            });

            function getUrlParam(paramName) {
                var reParam = new RegExp('(?:[\?&]|&amp;)' + paramName + '=([^&]+)', 'i');
                var match = window.location.search.match(reParam);

                return (match && match.length > 1) ? match[1] : '';
            }

            var funcNum = getUrlParam('CKEditorFuncNum');

            var options = {
                baseUrl: "/Assets/Plugins/elfinder/",
                url: "/elfinder/connector",
                rememberLastDir: false,
                commands: elFinderCommands,
                getFileCallback: function (file) {
                    window.opener.CKEDITOR.tools.callFunction(funcNum, file.url);
                    window.close();
                },
                uiOptions: {
                    toolbar: [
                        ['back', 'forward'],
                        ['reload'],
                        ['home', 'up'],
                        //['mkdir', 'mkfile', 'upload'],
                        ['open', 'download'],
                        ['undo', 'redo'],
                        ['info'],
                        ['quicklook'],
                        ['copy', 'cut', 'paste'],
                        ['rm'],
                        ['duplicate', 'rename', 'edit'],
                        ['selectall', 'selectnone', 'selectinvert'],
                        ['view', 'sort']
                    ]
                },
                onlyMimes: ["image"],
                lang: 'tr'
            };
            $('#elfinder').elfinder(options).elfinder('instance');
        });
    </script>
</body>
</html>

Öncelikle elFinder jquery ve juery.ui kütüpanelerine ihtiyaç duyuyor. Gördüğünüz gibi html olarak tek bir div'imiz var ve Id'si elfinder. Burada incelememiz gerek kısım js kodları.

elFinderCommands array değişkenimde elFinderin desteklediği bütün komutlar var.

disabledCommands değişkenimde ise pasif etmek istediğim komutları yazıyorum ve altındaki döngüde, elFinderCommands dizemden disabledCommands dizemle bulunan değerleri çıkartıyorum. Bu komutları kafama göre yazmadım elFinder.NetCore'un henüz bu komutları desteklemediğini okudum incelediğim yabancı kaynakta, bende aldığım gibi ekledim projeme. Tek tek test etmedim ama zipleme çalışıyordu yani archive komutu.

getUrlParam fonksiyonu urlden parametleri okuyan bir fonksiyon, ckediyor bu sayfayı çağırdığında bize urlden parametre gönderecek bizde bu fonksiyon yardımıyla okuyacağız.

funcNum değişkeni ckeditorun urlden yakaladığı parametreyi tutuyor.

options değişkeninde ise elFinder'ın tüm yapılandırmasını ayarlıyoruz.

baseUrl elFinderin projemizdeki fiziksel dizini. 

url ise connectorun yolu. 

rememberLastDir nedir bilmiyorum heralde açık kalan son dizini hatırla gibi bir şey isminden anladığım kadarıyla. 

commands dosya yöneticisinde kullanılacak tüm komutlar. 

getFileCallback ise dosyaya tıklandığında ne yapılacağı, biz burada elfinder popup'ını açan sayfadaki ckeditor'e dosyanın url'ini gönderiyoruz ve editörde resimleri kullanabilmeyi sağlıyoruz. Ckeditor ile ilgili tek kısım burası aslında.

toolbar parametresinde ise dosya yöneticisinde ki araç çubuklarını belirliyoruz. Ben bu örnek için ['mkdir', 'mkfile', 'upload'] parametlerini yorum satırına aldım. Yani ckeditör ile açılan dosya yöneticisinde klasör oluşturma, yükleme işlemlerini pasif ediyorum.

onlyMimes dosya tiplerini belirdediğimiz parametre, ckeditor için kullanacağımızdan sadece image tipindeki dosyalar gözüksün olarak ayarladım. 

lang arayüz dili. ben bu projede türkçe ve ingilizce dışındaki tüm dilleri sildim.

En son satırdada  $('#elfinder').elfinder(options).elfinder('instance'); metodunu çağırıp elfinder id'li divimize dosya yöneticisini iliştir diyoruz. FileManager view'ımda da aşağı yukarı aynı şeyleri yaptığım için tekrar aynı kodları buraya eklemiyorum. FileManager view'da sadece kısıtlamaları kaldırdık.

Elfinder ile ilgili işlemlerimizi bitirdik, şimdi Ckeditor'e geçelim. Ckeditor için HomeController'ı kullanacağım. Bu controller'ımda iki adet action olcak Index ve UploadImage Index direkt text editörümüzü kullanacağımız view'ı dönecek UploadImage'de Ckeditor üzerinden resim yükleme işlemlerimizi yapacak.

public class HomeController : Controller
{
	public IActionResult Index()
	{
		return View();
	}

	[HttpPost]
	public IActionResult UploadImage(IFormFile upload)
	{
		if (upload.Length <= 0) return null;

		var fileName = upload.FileName;
		var path = Path.Combine($"{Directory.GetCurrentDirectory()}\\Assets\\Images", fileName);

		using (var stream = new FileStream(path, FileMode.Create))
		{
			upload.CopyTo(stream);
		}

		var url = $"/Assets/Images/{fileName}";
		return Json(new { uploaded = true, url });
	}
}

Dosya yükleme işleminin detaylarına girmiyorum, özetle Assets/Images yoluna resminizi yükleyip, sonucu json formatında dönüyor. Bu yükleme işleminde aynı isimden dosya varsa dosya isminin yanına random bir sayı atıcak metodun da olaması gerekiyor ancak makalenin konusu olmadığı için o kısımlara girmedim.

Şimdi editörümüzün view kodlarına bakalım;

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
    <title>CkEditor elFinder</title>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>

<body>
    <textarea id="body" name="body"></textarea>
    <script src="~/Assets/Plugins/ckeditor/ckeditor.js"></script>
    <script>
        document.addEventListener("DOMContentLoaded", function (event) {
            CKEDITOR.replace('body', {
                filebrowserBrowseUrl: '/elfinder'
            });
        });
    </script>
</body>
</html>

Burada da gördüğünüz gibi sadece ckeditor.js yi ekledik, body id'sine sahip bir textarea ekledik. Son olarakta document'in DOMContentLoaded eventinda, yani sayfa yüklendiğinde CkEditor yapılandırmasının kodlarının çalışmasını sağladık. body id'sine ait textareyı ckeditore çevirecek ve dosya yöneticisi olarakta /elfinder url'ini kullanacak. Bu sayfadaki işlemlerimizde tamam. Son olarak ckeditor/config.js e aşağıdaki satırı ekleyip, yükleme işeminde hangi url'e post atacağını söylüyoruz.

config.filebrowserImageUploadUrl = '/Home/UploadImage'; 

Artık kullanım için herşey hazır, ckeditor üzerinden resim eklemek istediğimde sunucudaki resimleri elfinder aracılığıyla editörüme ekleyebiliyorum ve editor üzerinden resim upload işemi yapabiliyorum.

Bunun dışında /elfinder/file-manager url'i üzerinden tarayıcınızı bir dosya yöneticisi gibi kullanabilirsiniz.

Elfinder kaynak kodları: https://github.com/Studio-42/elFinder/

Elfinder dökümanı: https://github.com/Studio-42/elFinder/wiki

elFinder.NetCore Nuget Kaynak kodları: https://github.com/gordon-matt/elFinder.NetCore/

CkEditor dökümanı: https://ckeditor.com/docs/ckeditor4/

Projenin kaynak kodları: https://github.com/ekremozer/CoreMvcCkEditorElFinder