{"id":16428557,"url":"https://github.com/buraksenyurt/guide-for-aca","last_synced_at":"2026-03-07T01:01:16.250Z","repository":{"id":94515617,"uuid":"532764444","full_name":"buraksenyurt/guide-for-aca","owner":"buraksenyurt","description":"Applied Clean Architecture. Uygulamalı clean architecture eğitimlerinde kullanılmak üzere oluşturduğum repo. Basit ve hafifsiklet bir projenin temel enstrümanları ile uçtan uca hazırlanmasına yardımcı olan anlatımı içermektedir.","archived":false,"fork":false,"pushed_at":"2024-06-28T15:14:27.000Z","size":714,"stargazers_count":13,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-30T13:27:30.872Z","etag":null,"topics":["applied-clean-architecture","clean-architecture","csharp","dotnet","dotnet-v6","entity-framework","mediatr","sqlite3","vuejs","vuejs2"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/buraksenyurt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-09-05T06:03:44.000Z","updated_at":"2024-06-28T15:14:30.000Z","dependencies_parsed_at":"2024-10-28T15:28:51.239Z","dependency_job_id":"6e0aa20e-5062-4ecc-9570-8cc7580da3f6","html_url":"https://github.com/buraksenyurt/guide-for-aca","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/buraksenyurt/guide-for-aca","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buraksenyurt%2Fguide-for-aca","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buraksenyurt%2Fguide-for-aca/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buraksenyurt%2Fguide-for-aca/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buraksenyurt%2Fguide-for-aca/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/buraksenyurt","download_url":"https://codeload.github.com/buraksenyurt/guide-for-aca/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buraksenyurt%2Fguide-for-aca/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30204444,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-06T19:07:06.838Z","status":"ssl_error","status_checked_at":"2026-03-06T18:57:34.882Z","response_time":250,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["applied-clean-architecture","clean-architecture","csharp","dotnet","dotnet-v6","entity-framework","mediatr","sqlite3","vuejs","vuejs2"],"created_at":"2024-10-11T08:17:33.512Z","updated_at":"2026-03-07T01:01:16.213Z","avatar_url":"https://github.com/buraksenyurt.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Uygulamalı Clean Architecture - 101 _(Ubuntu Üstünde .Net 6 Odaklı)_\n\nApplied Clean Architecture. Uygulamalı clean architecture eğitimlerinde kullanılmak üzere oluşturduğum repo. Basit ve hafifsiklet bir projenin temel enstrümanları ile uçtan uca hazırlanmasına yardımcı olan anlatımı içermektedir. Materyal Ubuntu 20.04 sistemini baz almaktadır. Örnek senaryo bir şarkı sözü yazarının bestelerini yönetmek üzerine kurgulanmıştır. Eğitimin süresi 6 dersle sınırlıdır bu nedenle Redis Cache, Serilog, JWT Token ile Authentication entegrasyonları ile proje bazlı Unit Test'ler eklenmemiştir. Referans olarak önceki sürümlerde geliştirilmiş olan [scaling-robot](https://github.com/buraksenyurt/scaling-robot) projesine bakılabilir.\n\n- [Uygulamalı Clean Architecture - 101 _(Ubuntu Üstünde .Net 6 Odaklı)_](#uygulamalı-clean-architecture---101-ubuntu-üstünde-net-6-odaklı)\n  - [Kurulum (init.sh ile)](#kurulum-initsh-ile)\n  - [Kontrol](#kontrol)\n  - [00 - Çözüm İskeletinin Oluşturulması](#00---çözüm-i̇skeletinin-oluşturulması)\n  - [01 - Domain Projesine İlgili Tiplerin Eklenmesi](#01---domain-projesine-i̇lgili-tiplerin-eklenmesi)\n  - [02 - MediatR Paketinin Eklenmesi ve CQRS Hazırlıkları](#02---mediatr-paketinin-eklenmesi-ve-cqrs-hazırlıkları)\n  - [03 - DI Mekanizması ve Shared Geliştirmeleri](#03---di-mekanizması-ve-shared-geliştirmeleri)\n  - [04 - Data Tarafının Organizasyonu ve EF Migration İşleri](#04---data-tarafının-organizasyonu-ve-ef-migration-i̇şleri)\n  - [05 - Web API Projesi için Hazırlıklar](#05---web-api-projesi-için-hazırlıklar)\n  - [06 - Vue Tarafındaki Geliştirmeler](#06---vue-tarafındaki-geliştirmeler)\n- [07 Web Api tarafından Vue'nun Otomatik Olarak Çalışması](#07-web-api-tarafından-vuenun-otomatik-olarak-çalışması)\n  - [İpuçları](#i̇puçları)\n\n## Kurulum (init.sh ile)\n\nEğitim sırasında özellikle solution içeriğini oluşturmak ve projeler arası bağlantıları kurup gerekli paket yüklemelerini gerçekleştirmek biraz zaman kaybına neden oldu. İşleri kolaylaştırmak adına init.sh isimli bir bash dosyası hazırladım. Terminalden bu dosyayı çalıştırınca istediğimiz adda ve istediğimiz ana konu nesnesine(book, song, product gibi) özel bir çözüm oluşturabiliyoruz.\n\n![init_01.png](init_01.png)\n\n```bash\n# Ubuntu ortamımda dosyayı şu şekilde çalıştırdım\nsudo bash init.sh\n\n# Eğer klasörde yazma yetkileri gelmezse aşağıdaki gibi bir komut kullanılabilir\n# Ya da bunu parametrik olarak sh dosyası içerisine alabiliriz\nsudo chmod -R 777 Catalog/\n```\n\n## Kontrol\n\nİlk etapta sistem dotnet yüklü mü değil mi kontrol etmek lazım. Yoksa [şu adrese gidip sistemimize kurabiliriz](https://dotnet.microsoft.com/en-us/download) Ardından aşağıdaki terminal komutu ile dotnet sdk'in yüklü olup olmadığına bakabiliriz.\n\n```shell\ndotnet --version\n```\n\n## 00 - Çözüm İskeletinin Oluşturulması\n\nAşağıdaki komutlar ile işe başlanır.\n\n```shell\n## Backend tarafının inşası\n\nmkdir BalladMngr\ncd BalladMngr\ndotnet new sln\n\nmkdir src\ncd src\n\nmkdir core infrastructure presentation\n\ncd core\ndotnet new classlib -f net6.0 --name BalladMngr.Domain\ndotnet new classlib -f net6.0 --name BalladMngr.Application\n\ncd BalladMngr.Application\ndotnet add reference ../BalladMngr.Domain/BalladMngr.Domain.csproj\n\ncd ..\ncd ..\ncd infrastructure\n\ndotnet new classlib -f net6.0 --name BalladMngr.Data\ndotnet new classlib -f net6.0 --name BalladMngr.Shared\n\ncd BalladMngr.Data\ndotnet add reference ../../core/BalladMngr.Domain/BalladMngr.Domain.csproj\ndotnet add reference ../../core/BalladMngr.Application/BalladMngr.Application.csproj\n\ncd ..\ncd BalladMngr.Shared\nmkdir Services\ndotnet add reference ../../core/BalladMngr.Application/BalladMngr.Application.csproj\n\ncd ..\ncd ..\ncd presentation\n\ndotnet new webapi --name BalladMngr.WebApi\n\ncd BalladMngr.WebApi\n\ndotnet add reference ../../core/BalladMngr.Application/BalladMngr.Application.csproj\ndotnet add reference ../../infrastructure/BalladMngr.Data/BalladMngr.Data.csproj\ndotnet add reference ../../infrastructure/BalladMngr.Shared/BalladMngr.Shared.csproj\n\ncd ..\ncd ..\ncd ..\n\ndotnet sln add src/core/BalladMngr.Domain/BalladMngr.Domain.csproj\ndotnet sln add src/core/BalladMngr.Application/BalladMngr.Application.csproj\ndotnet sln add src/infrastructure/BalladMngr.Data/BalladMngr.Data.csproj\ndotnet sln add src/infrastructure/BalladMngr.Shared/BalladMngr.Shared.csproj\ndotnet sln add src/presentation/BalladMngr.WebApi/BalladMngr.WebApi.csproj\n\ncd src\ncd core\ncd BalladMngr.Domain\nmkdir Entities Enums Settings\ncd ..\ncd ..\n\n# Entity Framework için gerekli hazırlıklar\n\ndotnet tool install --global dotnet-ef\n\ncd presentation\ncd BalladMngr.WebApi\ndotnet add package Microsoft.EntityFrameworkCore.Design\n\ncd ..\ncd ..\ncd infrastructure\ncd BalladMngr.Data\ndotnet add package Microsoft.EntityFrameworkCore.Sqlite\n\nmkdir Contexts\n\n# Sqlite veri tabanı için migration operasyonları (Gerektiğinde Kullanılacak)\ndotnet ef migrations add InitialCreate --startup-project ../../presentation/BalladMngr.WebApi\ndotnet ef database update --startup-project ../../presentation/BalladMngr.WebApi\n\ncd ..\ncd ..\n\n# Vue Tarafının Oluşturulması\nsudo npm install -g @vue/cli\n\ncd presentation\nnpm install -g @vue/cli\n\n# Presentation klasörü altındayken vue projesi oluşturulur\nvue create web-app\n\n# \u003e Manually select features\n# İzleyen kısımda yapılacak seçimler (Choose Vue Version, Babel, Router, Vuex, Linter/Formatter)\n# Version olarak 2.x seçilir ki 3.x ile denemek lazım\n# Use history mode for router sorusuna Y(Yes) cevabı verilir\n# Ardından ESLint + Prettier seçilir\n# List on save seçili bırakılır\n# In dedicated config files seçili bırakılır\n\n# Material Design yaklaşımına uygun olarak vuetify kütüphanesi eklendi\n# web-app klasöründeyken aşağıdaki komut çalıştırılır\nvue add vuetify\n\n# Default(recommended) ile ilerlenir\n# Aşağıdaki komut ile vue tarafının çalıştığından emin olunur\nnpm run serve\n```\n\n## 01 - Domain Projesine İlgili Tiplerin Eklenmesi\n\nEnum sabitleri,\n\nStatus.cs\n\n```csharp\nnamespace BalladMngr.Domain.Enums\n{\n    //Şarkının güncel durumunu işaret eder.\n    public enum Status\n    {\n        Draft,\n        Completed\n    }\n}\n```\n\nLanguage.cs\n\n```csharp\nnamespace BalladMngr.Domain.Enums\n{\n    // Şarkının hangi dilde olduğunu tutar.\n    // Bestecimiz birden fazla dil biliyor :P\n    public enum Language\n    {\n        English,\n        Turkish,\n        Spanish\n    }\n}\n```\n\nve Entity veri yapıları\n\nSong.cs\n\n```csharp\nusing BalladMngr.Domain.Enums;\n\nnamespace BalladMngr.Domain.Entities\n{\n     // Beste ile ilgili temel bilgileri içerir.\n    public class Song\n    {\n        public int Id { get; set; }\n        public string Title { get; set; }\n        public string Lyrics { get; set; }\n        public Status Status { get; set; }\n        public Language Language { get; set; }\n    }\n}\n```\n\nUser.cs\n\n```csharp\nnamespace BalladMngr.Domain.Entities\n{\n    // İleride authentication gibi noktalarda kullanacağımız kullanıcı içindir.\n    public class User\n    {\n        public int UserId { get; set; }\n        public string Name { get; set; }\n        public string LastName { get; set; }\n        public string Email { get; set; }\n        public string Username { get; set; }\n        public string Password { get; set; }\n    }\n}\n```\n\nMail gönderim servisi içinde MailSettings.cs\n\n```csharp\nnamespace BalladMngr.Domain.Settings\n{\n    /*\n     * Shared projesine eklenecek EmailService, mail gönderme işini üstlenen bir servis.\n     * Mail gönderimi sırasında kimden gittiği, Smtp ayarları gibi bilgileri appSettings.json içeriğinden alsak hiç fena olmaz.\n     * Bu nedenle Domain altına appSettings altındaki MailSettings sekmesini işaret edecek bu sınıf eklendi.\n     */\n    public class MailSettings\n    {\n        public string From { get; set; }\n        public string SmtpHost { get; set; }\n        public int SmtpPort { get; set; }\n        public string SmtpUser { get; set; }\n        public string SmtpPass { get; set; }\n        public string DisplayName { get; set; }\n    }\n}\n```\n\n## 02 - MediatR Paketinin Eklenmesi ve CQRS Hazırlıkları\n\n```shell\ncd core\ncd BalladMngr.Application\ndotnet add package MediatR\ndotnet add package MediatR.Extensions.Microsoft.DependencyInjection\ndotnet add package Microsoft.Extensions.Logging.Abstractions\ndotnet add package AutoMapper\ndotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection\ndotnet add package FluentValidation\ndotnet add package FluentValidation.DependencyInjectionExtensions\ndotnet add package Microsoft.EntityFrameworkCore\ndotnet add package Microsoft.Extensions.Options.ConfigurationExtensions\n\nmkdir Common\ncd Common\nmkdir Behaviors Exceptions Interfaces Mappings\n\ncd ..\nmkdir Dtos\ncd Dtos\nmkdir Songs Email User\ncd ..\n\nmkdir Songs\ncd Songs\nmkdir Commands\ncd Commands\nmkdir CreateSong UpdateSong DeleteSong\ncd ..\n\nmkdir Queries\ncd Queries\nmkdir ExportSongs GetSongs\n```\n\nSerüven boyunca ihtiyacımız olacak bazı temel türleri ekleyelim. \n\nOlası özel exception tipleri...\n\nSongNotFoundException.cs\n\n```csharp\nusing System;\n\nnamespace BalladMngr.Application.Common.Exceptions\n{\n    public class SongNotFoundException\n        : Exception\n    {\n        public SongNotFoundException(int songId)\n          : base($\"{songId} nolu kitap envanterde bulunamadı\") { }\n    }\n}\n```\n\nSendEmailException.cs\n\n```csharp\nusing System;\n\nnamespace BalladMngr.Application.Common.Exceptions\n{\n    public class SendEmailException\n        : Exception\n    {\n        public SendEmailException(string message)\n            : base(message)\n        {\n        }\n    }\n}\n```\n\nValidationException.cs\n\n```csharp\nusing FluentValidation.Results;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\n\nnamespace BalladMngr.Application.Common.Exceptions\n{\n    /*\n     * \n     * ValidationBehavior tipi tarafından kullanılan Exception türevidir.\n     * \n     */\n    public class ValidationException\n        :Exception\n    {\n        public IDictionary\u003cstring, string[]\u003e Errors { get; }\n        public ValidationException()\n        {\n            Errors = new Dictionary\u003cstring, string[]\u003e();\n        }\n        public ValidationException(IEnumerable\u003cValidationFailure\u003e errors)\n            :this()\n        {\n            var errorGroups= errors\n                .GroupBy(e =\u003e e.PropertyName, e =\u003e e.ErrorMessage);\n\n            foreach (var e in errorGroups)\n            {\n                Errors.Add(e.Key, e.ToArray());\n            }\n        }\n\n    }\n}\n```\n\nYine serüven boyunca özel olarak MediatR tarafına entegre edeceğimiz bazı davranışlar var. Loglama, hata yakalama gibi. Bunları da Behaviors klasörüne sırasıyla ekleyelim.\n\nValidationBehavior.cs\n\n```csharp\nusing FluentValidation;\nusing MediatR;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace BalladMngr.Application.Common.Behaviors\n{\n    /*\n     * Bir Command çalışıp ekleme, güncelleme gibi işlemler yapıldığı sırada devreye giren doğrulama fonksiyonunu sağlıyoruz.\n     * \n     * Eğer talebin geldiği komuta bir Validator uygulanmışsa buradaki süreç çalışıp doğrulama ihlalleri toplanacak.\n     * Eğer ihlaller varsa bunu da ValidationException isimli bizim yazdığımı bir nesnede toplayıp sisteme exception basacağız.\n     * Doğal olarak talep işlenmeyecek.\n     */\n    public class ValidationBehavior\u003cTRequest, TResponse\u003e\n        : IPipelineBehavior\u003cTRequest, TResponse\u003e\n        where TRequest : IRequest\u003cTResponse\u003e\n    {\n        private readonly IEnumerable\u003cIValidator\u003cTRequest\u003e\u003e _validators;\n        public ValidationBehavior(IEnumerable\u003cIValidator\u003cTRequest\u003e\u003e validators)\n        {\n            _validators = validators;\n        }\n\n        public async Task\u003cTResponse\u003e Handle(TRequest request, RequestHandlerDelegate\u003cTResponse\u003e next,CancellationToken cancellationToken)\n        {\n            if (!_validators.Any())\n                return await next();\n\n            var context = new ValidationContext\u003cTRequest\u003e(request);\n            var validationResults = await Task.WhenAll(_validators.Select(v =\u003e v.ValidateAsync(context, cancellationToken)));\n            var errors = validationResults.SelectMany(r =\u003e r.Errors).Where(f =\u003e f != null).ToList();\n\n            if (errors.Count != 0)\n                throw new ValidationException(errors);\n\n            return await next();\n        }\n    }\n}\n```\n\nLoggingBehavior.cs\n\n```csharp\nusing MediatR.Pipeline;\nusing Microsoft.Extensions.Logging;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace BalladMngr.Application.Common.Behaviors\n{\n    /*\n     * MediatR işleyişinde mesajlar arasına girmek mümkün.\n     * Burada mesaj işlenmeden önce araya girip basitçe log atmaktayız.\n     */\n    public class LoggingBehavior\u003cTRequest\u003e\n        : IRequestPreProcessor\u003cTRequest\u003e\n    {\n        private readonly ILogger\u003cTRequest\u003e _logger;\n        public LoggingBehavior(ILogger\u003cTRequest\u003e logger)\n        {\n            _logger = logger;\n        }\n        public async Task Process(TRequest request, CancellationToken cancellationToken)\n        {\n            var requestName = typeof(TRequest).Name;\n            _logger.LogInformation($\"Talep geldi {request}\", requestName);\n        }\n    }\n}\n```\n\nPerformanceBehvaior.cs\n\n```csharp\nusing MediatR;\nusing Microsoft.Extensions.Logging;\nusing System.Diagnostics;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace BalladMngr.Application.Common.Behaviors\n{\n    /*\n     * Taleplerin ne kadar sürede işlendiğini tespit etmek ve belli bir değerin altına indiğinde uyarı logu basmak için bu uyarlama oldukça kullanışlı.\n     * LoggingBehavior'un uyguladığı arayüzden farklı bir arayüz uygulandığına dikkat etmek lazım.\n     * Handle metodunda mesajı hat üzerindeki bir sonraki noktaya taşıdığımız next fonksiyonunun önüne ve sonrasına zamanlayıcı çağrıları koyduk.\n     * Sonrasında cevaplama süresinin 250 milisaniye altında olup olmadığına bakıp uyarı logu basıyoruz.\n     */\n    public class PerformanceBehavior\u003cTRequest, TResponse\u003e\n        : IPipelineBehavior\u003cTRequest, TResponse\u003e\n        where TRequest : MediatR.IRequest\u003cTResponse\u003e\n    {\n        private readonly ILogger\u003cTRequest\u003e _logger;\n        private readonly Stopwatch _hgwells;\n        public PerformanceBehavior(ILogger\u003cTRequest\u003e logger)\n        {\n            _logger = logger;\n            _hgwells = new Stopwatch();\n        }\n        public async Task\u003cTResponse\u003e Handle(TRequest request, RequestHandlerDelegate\u003cTResponse\u003e next, CancellationToken cancellationToken)\n        {\n            _hgwells.Start();\n            var response = await next();\n            _hgwells.Stop();\n\n            var responseDuration = _hgwells.ElapsedMilliseconds;\n            if (responseDuration \u003c 250)\n                return response;\n\n            _logger.LogWarning($\"{typeof(TRequest).Name} normal çalışma süresini aştı. {responseDuration}. İçerik {request}\");\n            return response;\n\n        }\n    }\n}\n```\n\nson olarak UnhandledExceptionBehavior.cs\n\n```csharp\nusing MediatR;\nusing Microsoft.Extensions.Logging;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace BalladMngr.Application.Common.Behaviors\n{\n    /*\n     * Pipeline arayüzünden türeyen bu behavior tipi ile Query ve Command'lerin işleyişi sırasında oluşan exception durumlarını loglamaktayız.\n     * Yakalanan Exception akışta ele alınmamış bir exception ise burada yakalamamız kolay olacaktır.\n     */\n    public class UnhandledExceptionBehavior\u003cTRequest, TResponse\u003e\n        : IPipelineBehavior\u003cTRequest, TResponse\u003e\n        where TRequest : MediatR.IRequest\u003cTResponse\u003e\n    {\n        private readonly ILogger\u003cTRequest\u003e _logger;\n        public UnhandledExceptionBehavior(ILogger\u003cTRequest\u003e logger)\n        {\n            _logger = logger;\n        }\n        public async Task\u003cTResponse\u003e Handle(TRequest request, RequestHandlerDelegate\u003cTResponse\u003e next,CancellationToken cancellationToken)\n        {\n            try\n            {\n                return await next();\n            }\n            catch (Exception excp)\n            {\n                _logger.LogError(excp, \"Talep geldi ama bir exception oluştu\");\n                throw;\n            }\n        }\n    }\n}\n```\n\nŞimdi Mappings klasörüne IMapFrom.cs arayüzü eklenir.\n\n```csharp\nusing AutoMapper;\n\nnamespace BalladMngr.Application.Common.Mappings\n{\n    public interface IMapFrom\u003cT\u003e\n    {\n        void Mapping(Profile profile) =\u003e profile.CreateMap(typeof(T), GetType());\n    }\n}\n```\n\nAynı klasöre MappingProfile.cs eklenir.\n\n```csharp\nusing AutoMapper;\nusing System;\nusing System.Linq;\nusing System.Reflection;\n\nnamespace BalladMngr.Application.Common.Mappings\n{\n    public class MappingProfile : Profile\n    {\n        public MappingProfile()\n        {\n            var assembly = Assembly.GetExecutingAssembly();\n\n            var types = assembly.GetExportedTypes()\n              .Where(\n                        t =\u003e t.GetInterfaces()\n                        .Any(i =\u003e i.IsGenericType \u0026\u0026 i.GetGenericTypeDefinition() == typeof(IMapFrom\u003c\u003e))\n                    )\n              .ToList();\n\n            foreach (var type in types)\n            {\n                var instance = Activator.CreateInstance(type);\n                var methodInfo = type.GetMethod(\"Mapping\") ?? type.GetInterface(\"IMapFrom`1\").GetMethod(\"Mapping\");\n                methodInfo?.Invoke(instance, new object[] { this });\n            }\n        }\n    }\n}\n```\n\nŞimdi CQRS tarafını tamamlayalım.\n\nExportSongs klasörüne SongRecord.cs eklenir.\n\n```csharp\nusing BalladMngr.Application.Common.Mappings;\nusing BalladMngr.Domain.Entities;\nusing BalladMngr.Domain.Enums;\n\nnamespace BalladMngr.Application.Songs.Queries.ExportSongs\n{\n    /*\n     * CSV içerisine hangi şarkı bilgilerini tutacağımızı belirten veri yapısı.\n     * \n     * Bir nesne dönüşümü söz konusu olduğundan IMapFrom\u003cT\u003e uyarlaması var.\n     * \n     */\n    public class SongRecord\n        :IMapFrom\u003cSong\u003e\n    {\n        public string Title { get; set; }\n        public Status Status { get; set; }\n    }\n}\n```\n\nArdından Interfaces klasörüne IApplicationDbContext vs ICsvBuilder.cs sınıfları eklenir.\n\nIApplicationDbContext.cs\n\n```csharp\nusing BalladMngr.Domain.Entities;\nusing Microsoft.EntityFrameworkCore;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace BalladMngr.Application.Common.Interfaces\n{\n    /*\n     * Entity Framework Core context nesnesine ait servis sözleşmemiz.\n     * Onu da DI mekanizmasına kolayca dahil edip bağımlılığı çözümletebiliriz.\n     * Yani infrastructe katmanındaki Data kütüphanesindeki BalladMngrDbContext'i bu arayüz üstünden sisteme entegre edebileceğiz.\n     */\n    public interface IApplicationDbContext\n    {\n        public DbSet\u003cSong\u003e Songs { get; set; }\n        Task\u003cint\u003e SaveChangesAsync(CancellationToken cancellationToken);\n    }\n}\n```\n\nICsvBuilder.cs\n\n```csharp\nusing BalladMngr.Application.Songs.Queries.ExportSongs;\nusing System.Collections.Generic;\n\nnamespace BalladMngr.Application.Common.Interfaces\n{\n    /*\n     * CSV dosya çıktısı üreten servisin sözleşmesi\n     * IEmailService tarafında olduğu gibi bu hizmeti de DI mekanizmasına kolayca dahil edebileceğiz.\n     */\n    public interface ICsvBuilder\n    {\n        byte[] BuildFile(IEnumerable\u003cSongRecord\u003e songRecords);\n    }\n}\n```\n\nAynı yere ExportSongsQuery.cs ve ExportSongsViewModel.cs dosyaları eklenir.\n\nExportSongsViewModel.cs\n\n```csharp\nnamespace BalladMngr.Application.Songs.Queries.ExportSongs\n{\n    /*\n     * Şarkıların bir çıktısını CSV olarak verdiğimizde kullanılan ViewModel nesnemiz.\n     * Bunu ExportSongsQuery ve Handler tipi kullanmakta.\n     * Dosyanın adını, içeriğin tipini ve byte[] cinsinden içeriği tutuyor.\n     * Belki byte[] yerine 64 bit encode edilmiş string içerik de verebiliriz. \n     */\n    public class ExportSongsViewModel\n    {\n        public string FileName { get; set; }\n        public byte[] Content { get; set; }\n        public string ContentType { get; set; }\n    }\n}\n```\n\nExportSongsQuery.cs\n\n```csharp\nusing AutoMapper;\nusing AutoMapper.QueryableExtensions;\nusing BalladMngr.Application.Common.Interfaces;\nusing MediatR;\nusing Microsoft.EntityFrameworkCore;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace BalladMngr.Application.Songs.Queries.ExportSongs\n{\n    /*\n     * \n     * Query nesnemiz.\n     * Talebe karşılık bir ViewModel döndürüleceğini tanımlıyor.\n     * \n     */\n    public class ExportSongsQuery\n        : IRequest\u003cExportSongsViewModel\u003e\n    {\n    }\n\n    /*\n    * Handler tipimiz.\n    * Gelen sorguya ele alıp uygun ViewModel nesnesinin döndürülmesi sağlanıyor.\n    * \n    * Kullanması için gereken bağımlılıkları Constructor Injection ile içeriye alıyoruz.\n    * Buna göre CSV üretici, AutoMapper nesne dönüştürücü ve EF Core DbContext servislerini içeriye alıyoruz.\n    * \n    * Şarkı listesini çektiğimiz LINQ sorgusunda ProjectTo metodunu nasıl kullandığımız dikkat edelim.\n    * Listenin SongRecord tipinden nesnelere dönüştürülmesi noktasında AutoMapper'ın çalışma zamanı sorumlusu kimse o kullanılıyor olacak.\n    * Nitekim SongRecord tipinin IMapFrom\u003cSong\u003e tipini uyguladığını düşünecek olursak çalışma zamanı Song üstünden gelen özelliklerden eş düşenleri, SongRecord karşılıklarına alacak.\n    */\n    public class ExportSongsQueryHandler : IRequestHandler\u003cExportSongsQuery, ExportSongsViewModel\u003e\n    {\n        private readonly IApplicationDbContext _context;\n        private readonly IMapper _mapper;\n        private readonly ICsvBuilder _csvBuilder;\n        public ExportSongsQueryHandler(IApplicationDbContext context, IMapper mapper, ICsvBuilder csvBuilder)\n        {\n            _context = context;\n            _mapper = mapper;\n            _csvBuilder = csvBuilder;\n        }\n        public async Task\u003cExportSongsViewModel\u003e Handle(ExportSongsQuery request, CancellationToken cancellationToken)\n        {\n            var viewModel = new ExportSongsViewModel();\n\n            var list = await _context.Songs\n                .ProjectTo\u003cSongRecord\u003e(_mapper.ConfigurationProvider)\n                .ToListAsync(cancellationToken);\n\n            viewModel.FileName = \"Songs.csv\";\n            viewModel.ContentType = \"text/csv\";\n            viewModel.Content = _csvBuilder.BuildFile(list);\n\n            return await Task.FromResult(viewModel);\n        }\n    }\n}\n```\n\nŞarkı bilgilerini çekmek için kullanılacak query nesneleri de GetSongs klasörü altına eklenecekler. \n\nAma öncesinde ilgili Dto nesnesi de eklemeli.\n\nSongDto.cs\n\n```csharp\nusing AutoMapper;\nusing BalladMngr.Application.Common.Mappings;\nusing BalladMngr.Domain.Entities;\n\nnamespace BalladMngr.Application.Dtos.Songs\n{\n    /*\n     * Bu aslında bir Data Transfer Object tipi.\n     * \n     * Controller tarafından gelen istekleri ele alan MediatR nesneleri doğrudan Song tipi ile değil de,\n     * Daha az özelliği içeren SongDto tipi ile çalışıyor. Son kullanıcıya da bu içerik verilecek.\n     * \n     * Genelde Entity tipleri dolaşıma girdiğinde tüm özelliklerini sunmak istemediğimiz senaryolara dahil olabilir.\n     * ViewModel'in bu durumlarda tüm entity nesnesi ile çalışmak yerine gerçekten ihtiyaç duyduğu özellikleri barındıran bir nesne ile çalışması doğru yaklaşımdır.\n     * \n     * Bu DTO IMapFrom\u003cT\u003e arayüzünü kullanır. Bu kısım aslında AutoMapper'ın ilgilendiği arayüzdür.\n     * IMapFrom içindeki Mapping metodu burada ele alınmış ve Language özelliği için ekstra bir işlem eklenmiştir.\n     */\n    public class SongDto\n        : IMapFrom\u003cSong\u003e\n    {\n        public int Id { get; set; }\n        public string Title { get; set; }\n        public string Lyrics { get; set; }\n        public int Language { get; set; }\n        public void Mapping(Profile profile)\n        {\n            profile.CreateMap\u003cSong, SongDto\u003e()\n                .ForMember(dest =\u003e dest.Language\n                    , opt =\u003e opt.MapFrom(source =\u003e (int)source.Language)\n                    );\n        }\n    }\n}\n```\n\nSongsViewModel.cs\n\n```csharp\n\nusing BalladMngr.Application.Dtos.Songs;\nusing System.Collections.Generic;\n\nnamespace BalladMngr.Application.Songs.Queries.GetSongs\n{\n    /*\n     * Şarkı listesinin tamamını çeken Query'nin çalıştığı ViewModel nesnesi\n     * \n     */\n    public class SongsViewModel\n    {\n        public IList\u003cSongDto\u003e SongList { get; set; }\n    }\n}\n```\n\nve GetSongsQuery.cs\n\n```csharp\nusing AutoMapper;\nusing AutoMapper.QueryableExtensions;\nusing BalladMngr.Application.Common.Interfaces;\nusing BalladMngr.Application.Dtos.Songs;\nusing MediatR;\nusing Microsoft.EntityFrameworkCore;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace BalladMngr.Application.Songs.Queries.GetSongs\n{\n    /*\n     * Query ile tüm şarkı listesinin çekilmesi sürecini ele alıyoruz.\n     * \n     * Talebe karşılık SongsViewModel nesnesi dönülüyor ki o da içinde SongDto tipinden bir liste barındırmakta.\n     * \n     */\n    public class GetSongsQuery\n        : IRequest\u003cSongsViewModel\u003e\n    {\n    }\n\n    public class GetSongsQueryHandler\n        : IRequestHandler\u003cGetSongsQuery, SongsViewModel\u003e\n    {\n        private readonly IApplicationDbContext _context;\n        private readonly IMapper _mapper;\n        public GetSongsQueryHandler(IApplicationDbContext context, IMapper mapper)\n        {\n            _context = context;\n            _mapper = mapper;\n        }\n        public async Task\u003cSongsViewModel\u003e Handle(GetSongsQuery request, CancellationToken cancellationToken)\n        {\n            SongsViewModel Songs;\n\n            Songs = new SongsViewModel\n            {\n                SongList = await _context\n                .Songs\n                .ProjectTo\u003cSongDto\u003e(_mapper.ConfigurationProvider)\n                .OrderBy(b =\u003e b.Title)\n                .ToListAsync(cancellationToken)\n            }; // normal EF üstünden repository'ye gidip veriyi çekiyor\n\n            return Songs;\n        }\n    }\n}\n```\n\nEmail gönderim servisi ve authentication tarafı için Dtos klasörü altına gerekli Dto veri yapılarını da ekleyelim.\n\nEmailDto.cs\n\n```csharp\nnamespace BalladMngr.Application.Dtos.Email\n{\n    public class EmailDto\n    {\n        public string From { get; set; }\n        public string To { get; set; }\n        public string Subject { get; set; }\n        public string Body { get; set; }\n    }\n}\n```\n\nAuthenticationRequest.cs\n\n```csharp\nusing System.ComponentModel.DataAnnotations;\n\nnamespace BalladMngr.Application.Dtos.User\n{\n    /*\n     * Authentication aşamasında gelen talebin içinde Username ve Password bilgisinin taşınmasını sağlayan sınıfımız\n     */\n    public class AuthenticationRequest\n    {\n        [Required]\n        public string Username { get; set; }\n\n        [Required]\n        public string Password { get; set; }\n    }\n}\n```\n\nAuthenticationResponse.cs\n\n```csharp\nnamespace BalladMngr.Application.Dtos.User\n{\n    /*\n     * Doğrulama başarılı olduğunda dönen response içeriğini temsil eden sınıf.\n     * \n     * Aslında Claim sete ait bilgileri taşıdığını ifade edebiliriz.\n     * Önemli detaylardan birisi Password alanının olmayışı ama üretilecek JWT token için Token isimli bir özellik kullanılmasıdır.\n     */\n    public class AuthenticationResponse\n    {\n        public int UserId { get; set; }\n        public string Name { get; set; }\n        public string LastName { get; set; }\n        public string Username { get; set; }\n        public string Email { get; set; }\n        public string Token { get; set; }\n        public AuthenticationResponse(Domain.Entities.User user, string token)\n        {\n            UserId = user.UserId;\n            Name = user.Name;\n            LastName = user.LastName;\n            Username = user.Username;\n            Email = user.Email;\n            Token = token;\n        }\n    }\n}\n```\n\nEmail gönderim servisi ve doğrulama hizmeti için gerekli Dto nesneleri hazır. Artık dışarıya açılacak sözleşmeleri tanımlayabiliriz.\n\nIEmailService.cs\n\n```csharp\nusing BalladMngr.Application.Dtos.Email;\nusing System.Threading.Tasks;\n\nnamespace BalladMngr.Application.Common.Interfaces\n{\n    /*\n     * Email gönderim işlemini tanımlayan servis sözleşmesi.\n     * \n     * Sistemde buna benzer işlevsel fonksiyonlar içeren servisleri birer arayüz ile tanımlıyoruz.\n     * Böylece bağımlılıkları kolayca çözümletebiliriz. Dependency Injection uygulamak kolay olacaktır.\n     * Bu servisler Application katmanında toplanıyorlar. \n     * Interfaces isimli bir klasör içerisinde konuşlandırmak oldukça mantıklı.\n     * \n     */\n    public interface IEmailService\n    {\n        Task SendAsync(EmailDto mailDto);\n    }\n}\n```\n\nIUserService.cs\n\n```csharp\nusing BalladMngr.Application.Dtos.User;\nusing BalladMngr.Domain.Entities;\n\nnamespace BalladMngr.Application.Common.Interfaces\n{\n    /*\n     * Kullanıcı servisi response içeriği ile gelen bilgilere göre doğrulama işini üstlenen\n     * ve id üstünden kullanıcı bilgisi döndüren fonksiyonları tarifleyen bir sözleşme sunuyor.\n     */\n    public interface IUserService\n    {\n        AuthenticationResponse Authenticate(AuthenticationRequest model);\n        User GetById(int userId);\n    }\n}\n```\n\nTekrar CQRS tarafına dönelim ve Create, Update, Delete için gerekli komut sınıflarını oluşturalım.\n\nCreateSongCommand.cs\n\n```csharp\nusing BalladMngr.Application.Common.Interfaces;\nusing BalladMngr.Domain.Entities;\nusing BalladMngr.Domain.Enums;\nusing MediatR;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace BalladMngr.Application.Songs.Commands.CreateSong\n{\n    /*\n     * Şarkı listesine yeni bir beste ekleme işini üstlenen MediatR tipleri.\n     * Komut şarkı için gerekli parametreleri alırken geriye insert sonrası oluşan bir int değer(kuvvetle muhtemel primary key id değeri) dönüyor.\n     * Handler sınıfı IApplicationDbContext üstünden gelen Entity Context nesnesini kullanarak besteyi repository'ye ekliyor.\n     * \n     */\n    public class CreateSongCommand\n        : IRequest\u003cint\u003e\n    {\n        public string Title { get; set; }\n        public Language Language{ get; set; }\n        public string Lyrics { get; set; }\n    }\n\n    public class CreateSongCommandHandler\n        : IRequestHandler\u003cCreateSongCommand, int\u003e\n    {\n        private readonly IApplicationDbContext _context;\n        public CreateSongCommandHandler(IApplicationDbContext context)\n        {\n            _context = context;\n        }\n        public async Task\u003cint\u003e Handle(CreateSongCommand request, CancellationToken cancellationToken)\n        {\n            var s = new Song\n            {\n                Title = request.Title,\n                Lyrics=request.Lyrics,\n                Language=request.Language,\n                Status=Status.Draft\n            };\n            _context.Songs.Add(s);\n            await _context.SaveChangesAsync(cancellationToken);\n\n            return s.Id;\n        }\n    }\n}\n```\n\nBir şarkı eklenirken doğrulama işlemlerini de işletebiliriz. Bunun için,\n\nCreateSongCommandValidator.cs\n\n```csharp\nusing FluentValidation;\nusing BalladMngr.Application.Common.Interfaces;\n\nnamespace BalladMngr.Application.Songs.Commands.CreateSong\n{\n    /*\n     * Şarkı bilgilerinin güncellendiği Command nesnesi için hazırlanmış doğrulama tipi. \n     * Esasında CreateSongCommandValidator ile neredeyse aynı.\n     */\n    public class CreateSongCommandValidator : AbstractValidator\u003cCreateSongCommand\u003e\n    {\n        private readonly IApplicationDbContext _context;\n\n        public CreateSongCommandValidator(IApplicationDbContext context)\n        {\n            _context = context;\n\n            RuleFor(v =\u003e v.Title)\n              .NotEmpty().WithMessage(\"Şarkının başlığı olmalı!\")\n              .MaximumLength(100).WithMessage(\"Bu şarkının adı çok uzun!\");\n\n            RuleFor(v =\u003e v.Lyrics)\n              .NotEmpty().WithMessage(\"Birkaç mısra da olsa sözler olmalı!\")\n              .MinimumLength(50).WithMessage(\"Bence en az 50 karakterden oluşan bir metin olmalı!\")\n              .MaximumLength(1000).WithMessage(\"Ne çok söz varmış. Şarkıyı kısaltalım.\");\n        }\n    }\n}\n```\n\nDeleteSongCommand.cs\n\n```csharp\nusing BalladMngr.Application.Common.Exceptions;\nusing BalladMngr.Application.Common.Interfaces;\nusing MediatR;\nusing Microsoft.EntityFrameworkCore;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace BalladMngr.Application.Songs.Commands.DeleteSong\n{\n    /*\n     * beste silme işini ele aldığımı Command ve Handler tipleri.\n     * \n     * Bir besteyi silmek için talebin içinde Id bilgisinin olması yeterli. Command buna göre düzenlendi.\n     * Silme operasyonunu ele alan Handler tipimiz yine ilgili kitabı envanterde arıyor.\n     * Eğer bulamazsa SongNotFoundException fırlatılıyor.\n     * \n     */\n    public class DeleteSongCommand\n        : IRequest\n    {\n        public int SongId { get; set; }\n    }\n\n    public class DeleteSongCommandHandler\n        : IRequestHandler\u003cDeleteSongCommand\u003e\n    {\n        private readonly IApplicationDbContext _context;\n        public DeleteSongCommandHandler(IApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        public async Task\u003cUnit\u003e Handle(DeleteSongCommand request, CancellationToken cancellationToken)\n        {\n            var b = await _context.Songs\n              .Where(l =\u003e l.Id == request.SongId)\n              .SingleOrDefaultAsync(cancellationToken);\n\n            if (b == null)\n            {\n                throw new SongNotFoundException(request.SongId);\n            }\n\n            _context.Songs.Remove(b);\n            await _context.SaveChangesAsync(cancellationToken);\n\n            return Unit.Value;\n        }\n    }\n}\n```\n\nve güncelleme komutu için UpdateSongCommand.cs\n\n```csharp\nusing BalladMngr.Application.Common.Exceptions;\nusing BalladMngr.Application.Common.Interfaces;\nusing BalladMngr.Domain.Enums;\nusing MediatR;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace BalladMngr.Application.Songs.Commands.UpdateSong\n{\n    /*\n     * Bir şarkının bilgilerini güncellemek isteyebilirim. Adı, içeriği, durumu vesaire.\n     * Topluca bunların güncellemesini ele alan Command'ın beklediği özellikler aşağıdaki gibidir.\n     * \n     * Handler sınıfı da bu Command'i kullanarak repository üzerinde gerekli güncellemeleri yapar. \n     * Id ile gelen kitap bilgisi bulunamazsa ortama SongNotFoundException isimli bizim yazdığımız bir istisna tipi fırlatılır.\n     * \n     */\n    public partial class UpdateSongCommand : IRequest\n    {\n        public int SongId { get; set; }\n        public string Title { get; set; }\n        public string Lyrics { get; set; }\n        public Language Language { get; set; }\n        public Status Status { get; set; }\n    }\n\n    public class UpdateSongCommandHandler : IRequestHandler\u003cUpdateSongCommand\u003e\n    {\n        private readonly IApplicationDbContext _context;\n\n        public UpdateSongCommandHandler(IApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        public async Task\u003cUnit\u003e Handle(UpdateSongCommand request, CancellationToken cancellationToken)\n        {\n            var s = await _context.Songs.FindAsync(request.SongId);\n            if (s == null)\n            {\n                throw new SongNotFoundException(request.SongId);\n            }\n            s.Title = request.Title;\n            s.Lyrics = request.Lyrics;\n            s.Language = request.Language;\n            s.Status = request.Status;\n\n            await _context.SaveChangesAsync(cancellationToken);\n\n            return Unit.Value;\n        }\n    }\n}\n```\n\nGüncelleme işlemi sırasında da doğrulama operasyonu işletebiliriz. Bunun için\n\nUpdateSongCommandValidator.cs\n\n```csharp\nusing FluentValidation;\nusing BalladMngr.Application.Common.Interfaces;\n\nnamespace BalladMngr.Application.Songs.Commands.UpdateSong\n{\n    /*\n     * Şarkı bilgilerinin güncellendiği Command nesnesi için hazırlanmış doğrulama tipi. \n     * Esasında CreateSongCommandValidator ile neredeyse aynı.\n     */\n    public class UpdateSongCommandValidator : AbstractValidator\u003cUpdateSongCommand\u003e\n    {\n        private readonly IApplicationDbContext _context;\n\n        public UpdateSongCommandValidator(IApplicationDbContext context)\n        {\n            _context = context;\n\n            RuleFor(v =\u003e v.Title)\n              .NotEmpty().WithMessage(\"Şarkının başlığı olmalı!\")\n              .MaximumLength(100).WithMessage(\"Bu şarkının adı çok uzun!\");\n\n            RuleFor(v =\u003e v.Lyrics)\n              .NotEmpty().WithMessage(\"Birkaç mısra da olsa sözler olmalı!\")\n              .MinimumLength(50).WithMessage(\"Bence en az 50 karakterden oluşan bir metin olmalı!\")\n              .MaximumLength(1000).WithMessage(\"Ne çok söz varmış. Şarkıyı kısaltalım.\");\n        }\n    }\n}\n```\n\n## 03 - DI Mekanizması ve Shared Geliştirmeleri\n\nDI ayarlamaları için Application projesine DependencyInjection.cs eklenir.\n\nDependencyInjection.cs\n\n```csharp\nusing FluentValidation;\nusing BalladMngr.Application.Common.Behaviors;\nusing MediatR;\nusing MediatR.Pipeline;\nusing Microsoft.Extensions.DependencyInjection;\nusing System.Reflection;\nusing Microsoft.Extensions.Configuration;\n\nnamespace BalladMngr.Application\n{\n    /*\n     * Uygulama katmanının içindeki servisleri kullanan tarafa(örneğin Web API) bildirmek için kullandığımız bir sınıf olarak düşünebiliriz.\n     * IServiceCollection zaten .Net'in dahili DI servislerine ulaşmakta önemli bir aracı.\n     * AutoMapper, MediatR, Validation ve Behavior'ları servisler koleksiyonuna ekleyen bir metot.\n     * \n     * Bu sınıfı büyük ihtimalle Web API projesinin Startup'ındaki ConfigureServices metodunda kullanacağız. Böylece Web API çalışma zamanına\n     * gerekli DI bağımlılıkları otomatik olarak yüklenmiş olacak.\n     */\n    public static class DependencyInjection\n    {\n        public static IServiceCollection AddApplication(this IServiceCollection services, IConfiguration configuration)\n        {\n            services.AddAutoMapper(Assembly.GetExecutingAssembly());\n            services.AddMediatR(Assembly.GetExecutingAssembly());\n            services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());\n\n            services.AddTransient(typeof(IPipelineBehavior\u003c,\u003e), typeof(PerformanceBehavior\u003c,\u003e));\n            services.AddTransient(typeof(IRequestPreProcessor\u003c\u003e), typeof(LoggingBehavior\u003c\u003e));\n            services.AddTransient(typeof(IPipelineBehavior\u003c,\u003e), typeof(ValidationBehavior\u003c,\u003e));\n            services.AddTransient(typeof(IPipelineBehavior\u003c,\u003e), typeof(UnhandledExceptionBehavior\u003c,\u003e));\n\n            return services;\n        }\n    }\n}\n```\n\nCsv ve email işlemleri için bazı yardımcı paketlerden yararlanacağız. Application tarafında interface olarak tanımlanan bu sözleşmelerinin uygulayıcı Infrastructure katmanındaki Shared isimli kütüphane. Bu nedenle Shared isimli projeye aşağıdaki paketleri eklemeliyiz.\n\n```shell\ndotnet add package CsvHelper\ndotnet add package MailKit\ndotnet add package MimeKit\n```\n\nŞimdi Shared projesinin Services klasörüne CsvBuilder ve EmailService sınıfları eklenir.\n\nCsvBuilder.cs\n\n```csharp\nusing CsvHelper;\nusing BalladMngr.Application.Songs.Queries.ExportSongs;\nusing BalladMngr.Application.Common.Interfaces;\nusing System.Globalization;\n\nnamespace BalladMngr.Shared.Services\n{\n    /*\n     * Şarkı kaytılarını CSV dosyada geriye veren fonksiyonelliği içeren sınıfımız.\n     * Application içerisinde tanımladığımız servis sözleşmesini uyguladığına dikkat edelim.\n     * Yani servisin sözleşmesi Core katmanındaki Application projesinde, uyarlaması Infrastructure içerisindeki Shared projesinde.\n     */\n    public class CsvBuilder\n        : ICsvBuilder\n    {\n        public byte[] BuildFile(IEnumerable\u003cSongRecord\u003e songRecords)\n        {\n            using (var ms = new MemoryStream())\n            {\n                using (var writer = new StreamWriter(ms))\n                {\n                    using var csvWriter = new CsvWriter(writer, CultureInfo.InvariantCulture);\n                    csvWriter.WriteRecords(songRecords);\n                }\n                return ms.ToArray();\n            }\n        }\n    }\n}\n```\n\nEmailService.cs\n\n```csharp\nusing BalladMngr.Application.Common.Exceptions;\nusing BalladMngr.Application.Common.Interfaces;\nusing BalladMngr.Application.Dtos.Email;\nusing BalladMngr.Domain.Settings;\nusing MailKit.Net.Smtp;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Options;\nusing MimeKit;\n\nnamespace BalladMngr.Shared.Services\n{\n    /*\n     * Mail gönderme işini yapan asıl sınıf.\n     * Tahmin edileceği üzere tüm servis sözleşmelerinin olduğu Core katmanındaki Application projesindeki arayüzü kullanıyor.\n     * Loglama ve Email sunucu ayarlarını almak için gerekli bağımlılıklar Constructor üzerinden enjekte edilmekte.\n     * SendAsync metodu EmailDto isimli Data Transfer Object üstünden gelen bilgilerdeki kişiye mail olarak gönderiyor.\n     * Mail gönderimi sırasında bir istisna olması oldukça olası. Sonuçta bir SMTP sunucum bile yok. \n     * Bunu hata loglarında veya exception tablosunda ayrıca anlayabilmek için SendMailException isimli bir istisna tipi kullanılıyor.\n     */\n    public class EmailService\n        : IEmailService\n    {\n        private ILogger\u003cEmailService\u003e _logger { get; }\n        private MailSettings _mailSettings { get; }\n\n        public EmailService(IOptions\u003cMailSettings\u003e mailSettings, ILogger\u003cEmailService\u003e logger)\n        {\n            _mailSettings = mailSettings.Value;\n            _logger = logger;\n        }\n\n        public async Task SendAsync(EmailDto request)\n        {\n            try\n            {\n                var email = new MimeMessage\n                {\n                    Sender = MailboxAddress.Parse(request.From ?? _mailSettings.From)\n                };\n                email.To.Add(MailboxAddress.Parse(request.To));\n                email.Subject = request.Subject;\n                var builder = new BodyBuilder { HtmlBody = request.Body };\n                email.Body = builder.ToMessageBody();\n\n                using var smtp = new SmtpClient();\n                await smtp.SendAsync(email);\n                await smtp.DisconnectAsync(true);\n            }\n            catch (System.Exception ex)\n            {\n                _logger.LogError(ex.Message, ex);\n                throw new SendEmailException(ex.Message);\n            }\n        }\n    }\n}\n```\n\nSırada Shader kütüphanesi için gereki DI mekanizmasının entegrasyonu var. Bunun için projeye DependencyInjection sınıfını ekliyoruz.\n\nDependencyInjection.cs\n\n```csharp\nusing BalladMngr.Application.Common.Interfaces;\nusing BalladMngr.Domain.Settings;\nusing BalladMngr.Shared.Services;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace BalladMngr.Shared\n{\n    /*\n     * \n     * Çağıran ortamdaki DI servislerine Shared tarafından gelen bağımlılıkları yüklememizi kolaylaştıran sınıf.\n     * BalladMngr.Application'daki DependencyInjection sınıfı ile aynı teoriyi kullanıyor ve yine\n     * kuvvetle muhtemel buradaki servisleri tüketecek olan Web API projesindeki Startup sınıfındaki ConfigureServices içinden çağırılacak\n     */\n    public static class DependencyInjection\n    {\n        public static IServiceCollection AddShared(this IServiceCollection services, IConfiguration config)\n        {\n            services.Configure\u003cMailSettings\u003e(config.GetSection(\"MailSettings\"));\n            services.AddTransient\u003cIEmailService, EmailService\u003e();\n            services.AddTransient\u003cICsvBuilder, CsvBuilder\u003e();\n\n            return services;\n        }\n    }\n}\n```\n\n## 04 - Data Tarafının Organizasyonu ve EF Migration İşleri\n\nİlk olarak Context tiplerini eklemeliyiz.\n\nBalladMngrDbContext.cs\n\n```csharp\nusing BalladMngr.Application.Common.Interfaces;\nusing BalladMngr.Domain.Entities;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace BalladMngr.Data.Contexts\n{\n    /*\n     * DbContext nesnesi.\n     * IApplicationDbContext türetmesine de dikkat edelim. Core.Application katmanındaki sözleşmeyi kullanıyoruz.\n     * Bu DI servislerine Entity Context nesnesini eklerken işimize yarayacak.\n     * \n     */\n    public class BalladMngrDbContext\n        : DbContext, IApplicationDbContext\n    {\n        public BalladMngrDbContext(DbContextOptions\u003cBalladMngrDbContext\u003e options)\n            : base(options)\n        {\n        }\n\n        public DbSet\u003cSong\u003e Songs { get; set; }\n    }\n}\n```\n\nÖrnek verilerin girilmesini sağlayacak birde Seed sınıfı ekleyelim.\n\nBalladMngrDbContextSeed.cs\n\n```csharp\nusing BalladMngr.Domain.Entities;\nusing BalladMngr.Domain.Enums;\n\nnamespace BalladMngr.Data.Contexts\n{\n    /*\n     * Seed operasyonu için kullanılan statik sınıfımız.\n     * Eğer hiç şarkı yoksa birkaç tane ekleyecek.\n     */\n    public static class BalladMngrDbContextSeed\n    {\n        public static async Task SeedDataAsync(BalladMngrDbContext context)\n        {\n            if (!context.Songs.Any())\n            {\n                await context.Songs.AddAsync(new Song\n                {\n                    Title = \"The Wall\",\n                    Lyrics = @\"We don't need no education\n                                We don't need no thought control\n                                No dark sarcasm in the classroom\n                                Teacher, leave them kids alone\",\n                    Language = Language.English,\n                    Status = Status.Draft\n                });\n                await context.Songs.AddAsync(new Song\n                {\n                    Title = \"Mazeretim Var Asabiyim Ben\",\n                    Lyrics = @\"Gülmüyor yüzüm hayat zor oldu\n                                Güller susuz kurudu soldu\n                                Tövbe ettim gene bozuldu\n                                Yüreğim yanar\n                                Mazeretim var; asabiyim ben\n                                Mazeretim var; asabiyim ben\",\n                    Language = Language.Turkish,\n                    Status = Status.Completed\n                });\n                await context.Songs.AddAsync(new Song\n                {\n                    Title = \"Dönence\",\n                    Lyrics = @\"Simsiyah gecenin koynundayım yapayalnız\n                                Uzaklarda bir yerlerde güneşler doğuyor\n                                Biliyorum\n                                Dönence\",\n                    Language = Language.Turkish,\n                    Status = Status.Draft\n                });\n                await context.SaveChangesAsync();\n            }\n        }\n    }\n}\n```\n\nDiğer katmanlarda olduğu gibi burada da bağımlılıkları DI mekanizması ile çalışma zamanı için çözümleyeceğiz.\n\nDependencyInjection.cs\n\n```csharp\nusing BalladMngr.Application.Common.Interfaces;\nusing BalladMngr.Data.Contexts;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace BalladMngr.Data\n{\n    /*\n     * Çalışma zamanı DI servislerini Entity Framework DbContext türevimizi eklemek için kullanılan sınıf.\n     * \n     */\n    public static class DependencyInjection\n    {\n        public static IServiceCollection AddData(this IServiceCollection services,IConfiguration configuration)\n        {\n            services.AddDbContext\u003cBalladMngrDbContext\u003e(\n                options =\u003e options.UseSqlite(configuration.GetConnectionString(\"BalladMngrDbConnection\")) // SQLite veri tabanı bağlantı bilgisi konfigurasyon üstünden gelecek. Web API' nin appSettings.json dosyasından\n                );\n\n            // services.AddDbContext\u003cBalladMngrDbContext\u003e(\n            //     options =\u003e options.UseSqlServer(configuration.GetConnectionString(\"BalladMngrDbConnection\"))\n            //     );\n\n            services.AddScoped\u003cIApplicationDbContext\u003e(provider =\u003e provider.GetService\u003cBalladMngrDbContext\u003e());\n            return services;\n        }\n    }\n}\n```\n\n## 05 - Web API Projesi için Hazırlıklar\n\nController tarafında gerekli sınıfları ekleyelim.\n\nSongsController.cs\n\n```csharp\nusing BalladMngr.Application.Songs.Commands.CreateSong;\nusing BalladMngr.Application.Songs.Commands.DeleteSong;\nusing BalladMngr.Application.Songs.Commands.UpdateSong;\nusing BalladMngr.Application.Songs.Queries.GetSongs;\nusing MediatR;\nusing Microsoft.AspNetCore.Mvc;\n\nnamespace BalladMngr.WebApi.Controllers\n{\n    /*\n     * SongsController, şarkı ekleme, silme, güncelleme ve şarkı listesini ViewModel ölçütünde çekme işlemlerini üstleniyor.\n     * \n     * Constructor üstünden MediatR modülünün enjekte edildiğini görebiliyoruz.\n     * \n     * Önceki versiyonuna göre en büyük fark elbetteki metotlarda Query/Command nesnelerinin kullanılması.\n     * \n     * Ayrıca fonksiyon içeriklerine bakıldığında yapılan tek şey Send ile ilgili komutu MeditaR'a yönlendirmek. O gerisini halleder\n     * \n     */\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class SongsController : ControllerBase\n    {\n        private readonly IMediator _mediator;\n        public SongsController(IMediator mediator)\n        {\n            _mediator = mediator;\n        }\n\n        [HttpGet]\n        public async Task\u003cActionResult\u003cSongsViewModel\u003e\u003e Get()\n        {\n            return await _mediator.Send(new GetSongsQuery());\n        }\n\n        [HttpPost]\n        public async Task\u003cActionResult\u003cint\u003e\u003e Create(CreateSongCommand command)\n        {\n            return await _mediator.Send(command);\n        }\n\n        [HttpDelete(\"{id}\")]\n        public async Task\u003cActionResult\u003e Delete(int id)\n        {\n            await _mediator.Send(new DeleteSongCommand { SongId = id });\n            return NoContent();\n        }\n\n        [HttpPut(\"{id}\")]\n        public async Task\u003cActionResult\u003e Update(int id, UpdateSongCommand command)\n        {\n            if (id != command.SongId)\n                return BadRequest();\n            await _mediator.Send(command);\n\n            return NoContent();\n        }\n    }\n}\n```\n\nExportController.cs (CSV Export tarafı için)\n\n```csharp\nusing BalladMngr.Application.Songs.Queries.ExportSongs;\nusing MediatR;\nusing Microsoft.AspNetCore.Mvc;\n\nnamespace BalladMngr.WebApi.Controllers\n{\n    /*\n     * Şarkı listesini CSV formatında export etmemizi sağlayan Controller.\n     * \n     * SongsController'da olduğu gibi MediatR kullanıyor ve CSV çıktısı için ilgili Query komutunu işletiyor.\n     */\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class ExportController \n        : ControllerBase\n    {\n        private readonly IMediator _mediator;\n        public ExportController(IMediator mediator)\n        {\n            _mediator = mediator;\n        }\n\n        [HttpGet]\n        public async Task\u003cFileResult\u003e Get()\n        {\n            var model = await _mediator.Send(new ExportSongsQuery());\n            return File(model.Content, model.ContentType, model.FileName);\n        }\n    }\n}\n```\n\nUserController.cs (Auth tarafı için)\n\n```csharp\nusing BalladMngr.Application.Common.Interfaces;\nusing BalladMngr.Application.Dtos.User;\nusing Microsoft.AspNetCore.Mvc;\n\nnamespace BalladMngr.WebApi.Controllers\n{\n    /*\n     * Kullanıcı doğrulama işini üstlenen controller tipidir.\n     * Authenticat fonksiyonu /auth talebi ile çalışır ve Body içeriği ile gelen model nesnesindeki kullanıcı adı şifre üstünden UserService'e gidilir.\n     * Geçerli bir kullanıcı ise tamam ama değilse HTTP 400 Bad Request hatası basarız.\n     */\n    [ApiController]\n    [Route(\"api/[controller]\")]\n    public class UserController\n        : ControllerBase\n    {\n        private readonly IUserService _userService;\n        public UserController(IUserService userService) =\u003e _userService = userService;\n\n        [HttpPost(\"auth\")]\n        public IActionResult Authenticate([FromBody] AuthenticationRequest model)\n        {\n            var response = _userService.Authenticate(model);\n\n            if (response == null)\n                return BadRequest(new\n                {\n                    message = \"Kullanıcı adın ya da şifren hatalı!\"\n                });\n\n            return Ok(response);\n        }\n    }\n}\n```\n\nprogram.cs içeriği aşağıdaki gibi düzenlenir.\n\n```csharp\nusing BalladMngr.Application;\nusing BalladMngr.Data;\nusing BalladMngr.Data.Contexts;\nusing BalladMngr.Shared;\n\nvar builder = WebApplication.CreateBuilder(args);\n\nConfigurationManager configuration = builder.Configuration;\n\n// Add services to the container.\n\nbuilder.Services.AddControllers();\n// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle\nbuilder.Services.AddEndpointsApiExplorer();\nbuilder.Services.AddSwaggerGen();\n\n/*\n* Web API'nin çalışma zamanının ihtiyaç duyacağı Application,Data(Entity Framework context'ini alacak) ve Shared servislerini \n* aşağıdaki metotlar yardımıyla ekliyoruz.\n* \n* İlgili servisleri burada da açık bir şekilde ekleyebilirdik ancak yapmadık. \n* Bu sayede o kütüphanelerin servislerinin DI koleksiyonuna eklenme işini buradan soyutlamış olduk.\n* Orada servislerde bir değişiklik olursa buraya gelip bir şeyler yapmamıza gerek kalmayacak.\n* \n*/\nbuilder.Services.AddApplication(configuration);\nbuilder.Services.AddData(configuration);\nbuilder.Services.AddShared(configuration);\n\nvar app = builder.Build();\n\n// Configure the HTTP request pipeline.\nif (app.Environment.IsDevelopment())\n{\n    app.UseSwagger();\n    app.UseSwaggerUI();\n}\n\napp.UseHttpsRedirection();\n\napp.UseAuthorization();\n\napp.MapControllers();\n\nusing (var scope = app.Services.CreateScope())\n{\n    var context = scope.ServiceProvider.GetRequiredService\u003cBalladMngrDbContext\u003e();\n    await BalladMngrDbContextSeed.SeedDataAsync(context);\n}\n\napp.Run();\n```\n\nappsettings.json'da aşağıdaki gibi düzenlenir.\n\n```json\n{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft.AspNetCore\": \"Warning\"\n    }\n  },\n  \"ConnectionStrings\": {\n    \"BalladMngrDbConnection\": \"Data Source=BalladMngrDatabase.sqlite3\"\n  },\n  \"MailSettings\": {\n    \"From\": \"admin@blabal.bla\",\n    \"SmtpHost\": \"localhost\",\n    \"SmtpPort\": \"\",\n    \"SmtpPass\": \"\",\n    \"DisplayName\": \"Administrator\"\n  },\n  \"AllowedHosts\": \"*\"\n}\n```\n\nApi uygulamasını test etmeden önce Sqlite tarafı için gerekli migration işlemlerini başlatmak gerekecektir. Bunun için Infrastructure katmanındaki Data projesi klasöründeyken aşağıdaki komutlar çalıştırılır.\n\n```shell\ndotnet ef migrations add InitialCreate --startup-project ../../presentation/BalladMngr.WebApi\ndotnet ef database update --startup-project ../../presentation/BalladMngr.WebApi\n```\n\nBu işlemler sonucunda Data projesinde Migration klasörü, WebApi projesinde de sqlite3 uzantılı veri tabanı dosyası oluşur. Kontrol için bir Sqlite görüntüleme eklentisinden yararlanılabilir. Ayrıca web api projesi çalıştırıldıktan sonra https://localhost:7008/swagger/index.html adresine gidilerek ilgili testler yapılabilir.\n\n## 06 - Vue Tarafındaki Geliştirmeler\n\n```bash\n# Servis haberleşmesi için axios kullanacağız bu nedenle ilgili paketi install etmeliyiz\n# Bunu tabii ki ballad-mngr-app klasörü altında çalıştırmalıyız.\nsudo npm i axios\n\nsudo npm i vuelidate\n```\n\nvuelidate kullanımı için plugins klasörüne vuelidate.js eklenmelidir.\n\n```js\nimport Vue from \"vue\";\nimport Vuelidate from \"vuelidate\";\nVue.use(Vuelidate);\n```\n\nDoğrulama opsiyonları için src klasörü altına validators isimli bir klasör açıp index.js dosyasını ekleyelim.\n\n```js\nimport {\n    required,\n    minLength,\n    maxLength\n} from \"vuelidate/lib/validators\";\n\nexport default {\n    addSong: {\n        title: {\n            required, maxLength: maxLength(50)\n        },\n        lyrics: {\n            required, minLength: minLength(50), maxLength: maxLength(1000)\n        },\n    }\n};\n```\n\nTemel komponentleri ekleyelim. Bileşenleri components klasörü altına ekleyeceğiz.\n\nAddSongForm.vue\n\n```vue\n\u003ctemplate\u003e\n    \u003cv-row justify=\"center\"\u003e\n        \u003cv-dialog v-model=\"dialog\" persistent max-width=\"600px\"\u003e\n            \u003ctemplate v-slot:activator=\"{ on, attrs }\"\u003e\n                \u003cv-btn style=\"margin-top: 1rem\"\n                       rounded\n                       color=\"light-blue\"\n                       dark\n                       v-bind=\"attrs\"\n                       v-on=\"on\"\u003e\n                    \u003cv-icon left\u003emdi-plus\u003c/v-icon\u003e\n                    Koleksiyona yeni bir beste ekle.\n                \u003c/v-btn\u003e\n            \u003c/template\u003e\n            \u003cv-card\u003e\n                \u003cform @submit.prevent=\"\n            addSongAction(body);\n            body = {};\n          \"\u003e\n                    \u003cv-card-title\u003e\n                        \u003cspan class=\"headline\"\u003eŞarkı Bilgileri\u003c/span\u003e\n                    \u003c/v-card-title\u003e\n                    \u003cv-card-text\u003e\n                        \u003cv-container\u003e\n                            \u003cv-row\u003e\n                                \u003cv-col cols=\"12\" sm=\"6\"\u003e\n                                    \u003cv-text-field label=\"Adı\"\n                                                  v-model=\"body.title\"\n                                                  @input=\"$v.body.title.$touch()\"\n                                                  @blur=\"$v.body.title.$touch()\"\n                                                  :error-messages=\"titleErrors\"\n                                                  required\u003e\u003c/v-text-field\u003e\n                                \u003c/v-col\u003e\n                                \u003cv-col cols=\"12\"\u003e\n                                    \u003cv-textarea label=\"Sözleri\"\n                                                v-model=\"body.lyrics\"\n                                                @input=\"$v.body.lyrics.$touch()\"\n                                                @blur=\"$v.body.lyrics.$touch()\"\n                                                :error-messages=\"lyricsErrors\"\n                                                required\u003e\u003c/v-textarea\u003e\n                                \u003c/v-col\u003e\n\n                                \u003cv-col cols=\"12\" sm=\"6\"\u003e\n                                    \u003cv-select v-model=\"body.language\"\n                                              :items=\"languages\"\n                                              item-text=\"name\"\n                                              item-value=\"id\"\n                                              label=\"Dili\"\n                                              persistent-hint\n                                              return-object\n                                              single-line\u003e\n                                    \u003c/v-select\u003e\n                                \u003c/v-col\u003e\n                            \u003c/v-row\u003e\n                        \u003c/v-container\u003e\n                    \u003c/v-card-text\u003e\n                    \u003cv-card-actions\u003e\n                        \u003cv-spacer\u003e\u003c/v-spacer\u003e\n                        \u003cv-btn color=\"blue darken-1\" text @click=\"dialog = false\"\u003e\n                            Close\n                        \u003c/v-btn\u003e\n                        \u003cv-btn color=\"blue darken-1\"\n                               text\n                               @click=\"dialog = false\"\n                               :disabled=\"$v.$anyError\"\n                               type=\"submit\"\u003e\n                            Save\n                        \u003c/v-btn\u003e\n                    \u003c/v-card-actions\u003e\n                \u003c/form\u003e\n            \u003c/v-card\u003e\n        \u003c/v-dialog\u003e\n    \u003c/v-row\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\n    import { mapActions } from \"vuex\";\n    import validators from \"@/validators\";\n    export default {\n        name: \"AddSongForm\",\n        data: () =\u003e ({\n            body: {\n                title: \"\",\n                lyrics: \"\",\n                language: 1\n            },\n            dialog: false,\n            languages: [\n                { id: 0, name: \"İngilizce\" }\n                , { id: 1, name: \"Türkçe\" }\n                , { id: 2, name: \"İspanyolca\" }\n            ]\n        }),\n        methods: {\n            ...mapActions(\"SongModule\", [\"addSongAction\"]),\n        },\n        computed: {\n            titleErrors() {\n                const errors = [];\n                if (!this.$v.body.title.$dirty)\n                    return errors;\n                !this.$v.body.title.required \u0026\u0026 errors.push(\"Lütfen şarkının adını yaz.\");\n                !this.$v.body.title.maxLength \u0026\u0026 errors.push(\"En fazla 50 karakter.\");\n                return errors;\n            },\n            lyricsErrors() {\n                const errors = [];\n                if (!this.$v.body.lyrics.$dirty) return errors;\n                !this.$v.body.lyrics.required \u0026\u0026 errors.push(\"Lütfen şarkı sözlerini gir.\");\n                !this.$v.body.lyrics.minLength \u0026\u0026 errors.push(\"En az 50 karakter olsun\");\n                !this.$v.body.lyrics.maxLength \u0026\u0026 errors.push(\"En fazla 1000 karakter.\");\n                return errors;\n            },\n        },\n        validations: {\n            body: {\n                title: validators.addSong.title,\n                lyrics: validators.addSong.lyrics,\n            }\n        }\n    };\n\u003c/script\u003e\n```\n\nSongListCard.vue\n\n```Vue\n\u003ctemplate\u003e\n  \u003cv-skeleton-loader\n    v-if=\"loading\"\n    width=\"500\"\n    max-width=\"600\"\n    height=\"100%\"\n    type=\"card\"\n  \u003e\u003c/v-skeleton-loader\u003e\n\n  \u003cv-card v-else width=\"500\" max-width=\"600\" height=\"100%\"\u003e\n    \u003cv-toolbar color=\"pink\" dark\u003e\n      \u003cv-toolbar-title\u003eBesteler\u003c/v-toolbar-title\u003e\n      \u003cv-spacer\u003e\u003c/v-spacer\u003e\n    \u003c/v-toolbar\u003e\n    \u003cv-list-item-group color=\"primary\"\u003e\n      \u003cv-list-item v-for=\"song in songs\" :key=\"song.id\"\u003e\n        \u003cv-list-item-content\u003e\n          \u003cv-list-item-title v-text=\"song.title\"\u003e\u003c/v-list-item-title\u003e\n          \u003cv-list-item-subtitle v-text=\"song.status\"\u003e\u003c/v-list-item-subtitle\u003e\n        \u003c/v-list-item-content\u003e\n        \u003cv-list-item-action\u003e\n          \u003cv-icon @click=\"removeSong(song.id)\"\u003e mdi-delete-outline \u003c/v-icon\u003e\n        \u003c/v-list-item-action\u003e\n      \u003c/v-list-item\u003e\n    \u003c/v-list-item-group\u003e\n  \u003c/v-card\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\nimport { mapActions, mapGetters } from \"vuex\";\nexport default {\n  name: \"SongListCard\",\n  computed: {\n    ...mapGetters(\"songModule\", {\n      songs: \"songs\",\n      loading: \"loading\",\n    }),\n  },\n  methods: {\n    ...mapActions(\"songModule\", [\"removeSongAction\"]),\n    removeSong(songId) {\n      const confirmed = confirm(\"Bu besteyi silmek istediğine emin misin?\");\n      if (!confirmed) return;\n      this.removeSongAction(songId);\n    },\n  },\n};\n\u003c/script\u003e\n```\n\nNavigationBar.vue\n\n```Vue\n\u003ctemplate\u003e\n    \u003cv-app-bar app color=\"primary\" light\u003e\n      \u003cdiv\u003e\n        \u003cv-btn color=\"primary\" outlined :to=\"{ path: '/' }\"\u003e\n          \u003cspan class=\"menu\"\u003eLobi\u003c/span\u003e\n        \u003c/v-btn\u003e\n  \n        \u003crouter-link to=\"/about\"\u003e\n          \u003cv-btn color=\"primary\" outlined\u003e\n            \u003cspan class=\"menu\"\u003eBilgi\u003c/span\u003e\n          \u003c/v-btn\u003e\n        \u003c/router-link\u003e\n\n        \u003cv-btn\n          color=\"primary\"\n          outlined\n          :to=\"{ path: '/dashboard' }\"\n        \u003e\n          \u003cspan class=\"menu\"\u003eBesteler\u003c/span\u003e\n        \u003c/v-btn\u003e\n      \u003c/div\u003e\n    \u003c/v-app-bar\u003e\n  \u003c/template\u003e\n  \n  \u003cscript\u003e\n  export default {\n    name: \"NavigationBar\"    \n  };\n  \u003c/script\u003e\n  \n  \u003cstyle scoped\u003e\n  .menu {\n    color: white;\n    text-decoration: none;\n  }\n  \u003c/style\u003e\n```\n\nViews isimli klasör altında dashboard isimli bir klasör açalım. Ardından buraya aşağıdaki view nesnelerini ekleyelim.\n\nSongList.vue\n\n```vue\n\u003ctemplate\u003e\n  \u003cv-container\u003e\n    \u003cdiv class=\"text-h4 mb-10\"\u003eBeste Listesi\u003c/div\u003e\n    \u003cdiv class=\"v-picker--full-width d-flex justify-center\" v-if=\"loading\"\u003e\n      \u003cv-progress-circular\n        :size=\"70\"\n        :width=\"7\"\n        color=\"purple\"\n        indeterminate\n      \u003e\u003c/v-progress-circular\u003e\n    \u003c/div\u003e\n\n    \u003cv-simple-table\u003e\n      \u003ctemplate v-slot:default\u003e\n        \u003cthead\u003e\n          \u003ctr\u003e\n            \u003cth\u003eTitle\u003c/th\u003e\n            \u003cth\u003eLanguage\u003c/th\u003e\n          \u003c/tr\u003e\n        \u003c/thead\u003e\n        \u003ctbody\u003e\n          \u003ctr v-for=\"b in songs\" :key=\"b.id\"\u003e\n            \u003ctd\u003e{{ b.title }}\u003c/td\u003e\n            \u003ctd\u003e{{ getLang(b.language) }}\u003c/td\u003e\n          \u003c/tr\u003e\n        \u003c/tbody\u003e\n      \u003c/template\u003e\n    \u003c/v-simple-table\u003e\n  \u003c/v-container\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\nimport { mapActions, mapGetters } from \"vuex\";\nexport default {\n  name: \"songList\",\n  async mounted() {\n    await this.getSongsAction();\n    this.songList = this.songs.map((pl) =\u003e pl);\n  },\n  data() {\n    return {\n      songList: [],\n      loading: false,\n    };\n  },\n  methods: {\n    ...mapActions(\"songModule\", [\"getSongsAction\"]),\n    getLang(language) {\n      switch (language) {\n        case 0:\n          return \"İngilizce\";\n        case 1:\n          return \"Türkçe\";\n        case 2:\n          return \"İspanyolca\";\n        default:\n          return \"Bilemedim\";\n      }\n    },\n  },\n  computed: {\n    ...mapGetters(\"songModule\", {\n      songs: \"songs\",\n    }),\n  },\n};\n\u003c/script\u003e\n\n```\n\nDefaultContent.vue\n\n```vue\n\u003ctemplate\u003e\n    \u003cdiv\u003e\n        \u003cdiv class=\"text-h2 my-4\"\u003eBesteler Panosu\u003c/div\u003e\n        \u003cdiv class=\"default-content\"\u003e\n            \u003cdiv style=\"margin-right: 4rem; margin-bottom: 4rem\"\u003e\n                \u003cSongListCard @handleShowSongs=\"handleShowSongs\" /\u003e\n                \u003cAddSongForm /\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n        \u003cdiv v-if=\"showSongs\"\u003e\n            \u003cAddSongForm :songId=\"songId\" /\u003e\n        \u003c/div\u003e\n    \u003c/div\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\n    import { mapActions } from \"vuex\";\n    import SongListCard from \"@/components/SongListCard\";\n    import AddSongForm from \"@/components/AddSongForm\";\n    export default {\n        name: \"DefaultContent\",\n        components: {\n            SongListCard,\n            AddSongForm\n        },\n        methods: {\n            ...mapActions(\"songModule\", [\"getSongsAction\"]),\n            handleShowSongs(show, id) {\n                this.showSongs = show;\n                this.songId = id;\n            },\n        },\n        data: () =\u003e ({\n            showSongs: false,\n            songId: 0\n        }),\n        mounted() {\n            this.getSongsAction();\n            this.showSongs = false;\n        },\n    };\n\u003c/script\u003e\n\n\n\u003cstyle scoped\u003e\n    .default-content {\n        display: flex;\n        flex-direction: row;\n        flex-wrap: wrap;\n        justify-content: flex-start;\n    }\n\u003c/style\u003e\n```\n\nindex.vue\n\n```vue\n\u003ctemplate\u003e\n    \u003cv-sheet\n      height=\"100vh\"\n      class=\"overflow-hidden\"\n      style=\"display: flex; flex-direction: row; justify-content: flex-start\"\n    \u003e\n      \u003cv-navigation-drawer permanent expand-on-hover\u003e\n        \u003cv-list class=\"d-flex flex-column justify-center align-center\"\u003e\n          \u003cv-list-item class=\"px-2\"\u003e\n            \u003cv-list-item-avatar\u003e\n              \u003cv-img\n                src=\"https://randomuser.me/api/portraits/women/27.jpg\"\n              \u003e\u003c/v-img\u003e\n            \u003c/v-list-item-avatar\u003e\n          \u003c/v-list-item\u003e\n        \u003c/v-list\u003e\n\n        \u003cv-divider\u003e\u003c/v-divider\u003e\n  \n        \u003cv-list nav dense\u003e\n          \u003crouter-link to=\"/dashboard/\" class=\"menu link\"\u003e\n            \u003cv-list-item link\u003e\n              \u003cv-list-item-icon\u003e\n                \u003cv-icon\u003emdi-view-dashboard-variant-outline\u003c/v-icon\u003e\n              \u003c/v-list-item-icon\u003e\n              \u003cv-list-item-title\u003eDashboard\u003c/v-list-item-title\u003e\n            \u003c/v-list-item\u003e\n          \u003c/router-link\u003e\n          \u003crouter-link to=\"/dashboard/song-list\" class=\"menu link\"\u003e\n            \u003cv-list-item link\u003e\n              \u003cv-list-item-icon\u003e\n                \u003cv-icon\u003emdi-format-list-bulleted\u003c/v-icon\u003e\n              \u003c/v-list-item-icon\u003e\n              \u003cv-list-item-title\u003e Besteler Listesi \u003c/v-list-item-title\u003e\n            \u003c/v-list-item\u003e\n          \u003c/router-link\u003e          \n        \u003c/v-list\u003e\n      \u003c/v-navigation-drawer\u003e\n      \u003cv-container\u003e\n        \u003crouter-view /\u003e\n      \u003c/v-container\u003e\n    \u003c/v-sheet\u003e\n  \u003c/template\u003e\n  \n  \u003cscript\u003e\n  \n  export default {\n    name: \"dashboard-view\",\n  };\n  \u003c/script\u003e\n  \n  \u003cstyle scoped\u003e\n  .link {\n    text-decoration: none;\n  }\n  \u003c/style\u003e\n```\n\nAboutView.vue' yu şöyle değiştirelim.\n\n```vue\n\u003ctemplate\u003e\n    \u003cdiv class=\"about\"\u003e\n      \u003ch1\u003eBestelerim\u003c/h1\u003e\n      \u003cp\u003e\n        Test amaçlı bir açıklama gelir.\n      \u003c/p\u003e\n    \u003c/div\u003e\n  \u003c/template\u003e\n  ```\n\nHomeView.vue'yu da şöyle.\n\n```vue\n\u003ctemplate\u003e\n  \u003cdiv class=\"home fill-height d-flex justify-center align-center\"\u003e\n    \u003ch1\u003eCanım Şarkılarım :P\u003c/h1\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\nexport default {\n  name: \"home-view\",\n};\n\u003c/script\u003e\n```\n\nsrc altına api klasörü açılır ve gerekli servisler bağlanır.\n\nsong-service.js\n\n```js\nimport api from \"@/api/api-config\";\n\nexport async function getSongsAxios() {\n  return await api.get(`Songs/`);\n}\n```\n\napi-config.js\n\n```js\nimport axios from \"axios\";\nconst debug = process.env.NODE_ENV !== \"production\";\n\n/*\n    Üretim ortamında değilse localhost'tan üretim ortamındaysak gerçek adresinden bağlanacağımız bir axios nesnesi üretiliyor\n*/\nconst baseURL = debug\n  ? \"https://localhost:5001/api\"\n  : \"https://mylibrary.somewhere/api\";\n\nlet api = axios.create({ baseURL });\n\nexport default api;\n```\n\nrouter klasöründeki index.js'i aşağıdaki gibi değiştirelim.\n\n```js\nimport Vue from \"vue\";\nimport VueRouter from \"vue-router\";\nimport Home from \"@/views/Home.vue\";\nimport SongList from \"@/views/dashboard/SongList\";\n\nVue.use(VueRouter);\n\nconst routes = [\n  {\n    path: \"/\",\n    name: \"Home\",\n    component: Home,\n  },\n  {\n    path: \"/about\",\n    name: \"About\",\n    component: () =\u003e\n      import(/* webpackChunkName: \"about\" */ \"../views/About.vue\"),\n    meta: {\n      requiresAuth: false, // Hakkında sayfası için authorization gerekli değil.\n    },\n  },\n  {\n    path: \"/dashboard\",\n    component: () =\u003e import(\"@/views/dashboard\"),\n    meta: {\n      requiresAuth: false\n    },\n    children: [\n      {\n        path: \"\",\n        component: () =\u003e import(\"@/views/dashboard/DefaultContent\"),\n      },\n      {\n        path: \"song-list\",\n        component: SongList,\n      }\n    ],\n  }    \n];\n\nconst router = new VueRouter({\n  mode: \"history\",\n  base: process.env.BASE_URL,\n  routes,\n});\n\nexport default router;\n```\n\nstore klasörü içeriğini aşağıdaki gibi düzenleyelim.\n\n```bash\ncd store\nmkdir song\ncd song\ntouch action-types.js actions.js getters.js mutations.js services.js state.js index.js\n```\n\naction-types.js aşağıdaki gibi yazılır.\n\n```js\nexport const LOADING_SONGS = \"LOADING_SONGS\";\nexport const GET_SONGS = \"GET_SONGS\";\nexport const REMOVE_SONG = \"REMOVE_SONG\";\nexport const ADD_SONG = \"ADD_SONG\";\n```\n\nstate.js\n\n```js\nconst state = {\n    songs: [],\n    loading: false\n  };\n  \n  export default state;\n```\n\nmutations.js\n\n```js\nimport * as actionTypes from \"./action-types\";\n\nconst mutations = {\n    [actionTypes.GET_SONGS](state, songs) {\n        state.songs = songs;\n    },\n\n    [actionTypes.LOADING_SONGS](state, value) {\n        state.loading = value;\n    },\n\n    [actionTypes.REMOVE_SONG](state, id) {\n        state.songs = state.songs.filter((tl) =\u003e tl.id !== id);\n    },\n\n    [actionTypes.ADD_SONG](state, newSong) {\n        state.songs.unshift(newSong);\n    }\n};\n\nexport default mutations;\n```\n\ngetters.js\n\n```js\nconst getters = {\n    songs: state =\u003e state.songs,\n    loading: state =\u003e state.loading\n};\nexport default getters;\n```\n\nservices.js\n\n```js\nimport api from \"@/api/api-config\";\n\n/*\n   api-config üstünden api tarafında CRUD taleplerini gönderen fonksiyonları içerir\n*/\nexport async function getSongs() {\n  return await api.get(\"songs\");\n}\n\nexport async function deleteSong(id) {\n  return await api.delete(\"songs/\" + id);\n}\n\nexport async function addSong(newSong) {\n    return await api.post(\"songs\", newSong);\n}\n```\n\nactions.js\n\n```js\nimport * as actionTypes from \"./action-types\";\nimport { getSongs, deleteSong, addSong } from \"@/store/song/services\";\n\nexport async function getSongsAction({ commit }) {\n    // Şarkıların yüklendiğine dair bir durum bildiriyor\n    commit(actionTypes.LOADING_SONGS, true);\n\n    try {\n        // servis fonksiyonundan veri çekiliyor\n        const { data } = await getSongs();\n        // şarkı listesinin alınması farklı bir durum ve payload olarak da listenin kendisi bildiriliyor\n        commit(actionTypes.GET_SONGS, data.songList);\n    } catch (e) {\n        console.log(e);\n    }\n    // Şarkıların yüklenme durumu sona erdiği için false ile bir durum bilgilendirilmesi yapılıyor\n    commit(actionTypes.LOADING_SONGS, false);\n}\n\n// Listeden şarkı çıkarmak için kullanılan fonksiyon\nexport async function removeSongAction({ commit }, payload) {\n    commit(actionTypes.LOADING_SONGS, true);\n\n    try {\n        await deleteSong(payload);\n        commit(actionTypes.REMOVE_SONG, payload);\n    } catch (e) {\n        console.log(e);\n    }\n\n    commit(actionTypes.LOADING_SONGS, false);\n}\n\n// Yeni bir şarkı eklemek için kullanılan fonksiyon\nexport async function addSongAction({ commit }, payload) {\n    var langs = {\n        English: 0,\n        Turkish: 1,\n        Spanish: 2\n    };\n    switch (payload.language.id) {\n        case 0:\n            payload.language = langs.English;\n            break;\n        case 1:\n            payload.language = langs.Turkish;\n            break;\n        case 2:\n            payload.language = langs.Spanish;\n            break;\n        default:\n            payload.language = langs.Turkish;\n    }\n    commit(actionTypes.LOADING_SONGS, true);\n\n    try {\n        const { data } = await addSong(payload);\n        payload.id = data;\n        commit(actionTypes.ADD_SONG, payload);\n    } catch (e) {\n        console.log(e);\n    }\n\n    commit(actionTypes.LOADING_SONGS, false);\n}\n```\n\nindex.js\n\n```js\nimport state from \"./state\";\nimport getters from \"./getters\";\nimport mutations from \"./mutations\";\nimport * as actions from \"./actions\";\n\nexport default {\n    namespaced: true,\n    getters,\n    mutations,\n    actions,\n    state\n};\n```\n\nve pek tabii song altındaki bileşenleri dışarıya açan index dosyası _(store altındaki)_\n\n```js\nimport Vue from \"vue\";\nimport Vuex from \"vuex\";\nimport createLogger from \"vuex/dist/logger\";\nimport songModule from \"./song\";\n\nVue.use(Vuex);\n\nconst debug = process.env.NODE_ENV !== \"production\";\nconst plugins = debug ? [createLogger({})] : [];\n\nexport default new Vuex.Store({\n  modules: {\n    songModule,\n  },\n  plugins,\n});\n```\n\nBu işlemler ardından App.vue aşağıdaki gibi düzenlenebilir.\n\n```vue\n\u003ctemplate\u003e\n  \u003cv-app\u003e\n    \u003cNavigationBar /\u003e \n    \u003cv-main\u003e\n      \u003crouter-view /\u003e\n    \u003c/v-main\u003e\n  \u003c/v-app\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\nimport NavigationBar from \"@/components/NavigationBar\";\nexport default {\n  name: \"App\",\n  components: {\n    NavigationBar,\n  },\n};\n\u003c/script\u003e\n```\n\nmain.js içeriği de şöyle düzenlenebilir.\n\n```js\nimport Vue from \"vue\";\nimport App from \"./App.vue\";\nimport router from \"./router\";\nimport store from \"./store\";\nimport vuetify from \"./plugins/vuetify\";\nimport \"./plugins/vuelidate\"; // Doğrulama paketini projede kullanabilmek için eklendi\n\nVue.config.productionTip = false;\n\nnew Vue({\n  router,\n  store,\n  vuetify,\n  render: (h) =\u003e h(App),\n}).$mount(\"#app\");\n```\n\n# 07 Web Api tarafından Vue'nun Otomatik Olarak Çalışması\n\nWeb Api ve Vue tarafının Single Page Application olarak çalışması için bir şeyler yapabiliriz. Öncelikle WebApi projesinde ilgili paketi eklemek lazım.\n\n```bash\ndotnet add package VueCliMiddleware\n```\n\nArdından WebApi projesinde program.cs'de bazı değişiklikler gerekiyor. Program.cs'in son hali şöyle olacaktır.\n\n```csharp\nusing BalladMngr.Application;\nusing BalladMngr.Data;\nusing BalladMngr.Data.Contexts;\nusing BalladMngr.Shared;\nusing VueCliMiddleware;\n\nvar builder = WebApplication.CreateBuilder(args);\n\nvar configuration = builder.Configuration;\n\n// Add services to the container.\n\nbuilder.Services.AddControllers();\n// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle\nbuilder.Services.AddEndpointsApiExplorer();\nbuilder.Services.AddSwaggerGen();\n\nbuilder.Services.AddSpaStaticFiles(configuration =\u003e\n{\n    configuration.RootPath = \"../web-app/dist\";\n});\n\n/*\n* Web API'nin çalışma zamanının ihtiyaç duyacağı Application,Data(Entity Framework context'ini alacak) ve Shared servislerini \n* aşağıdaki metotlar yardımıyla ekliyoruz.\n* \n* İlgili servisleri burada da açık bir şekilde ekleyebilirdik ancak yapmadık. \n* Bu sayede o kütüphanelerin servislerinin DI koleksiyonuna eklenme işini buradan soyutlamış olduk.\n* Orada servislerde bir değişiklik olursa buraya gelip bir şeyler yapmamıza gerek kalmayacak.\n* \n*/\nbuilder.Services.AddApplication(configuration);\nbuilder.Services.AddData(configuration);\nbuilder.Services.AddShared(configuration);\n\nvar app = builder.Build();\n\n// Configure the HTTP request pipeline.\nif (app.Environment.IsDevelopment())\n{\n    app.UseSwagger();\n    app.UseSwaggerUI();\n}\n\napp.UseHttpsRedirection();\n\napp.UseAuthorization();\n\napp.MapControllers();\n\nusing (var scope = app.Services.CreateScope())\n{\n    var context = scope.ServiceProvider.GetRequiredService\u003cBalladMngrDbContext\u003e();\n    await BalladMngrDbContextSeed.SeedDataAsync(context);\n}\n\napp.UseSpa(spa =\u003e\n{\n    spa.Options.SourcePath = \"../ballad-mngr-app\";\n\n    if (app.Environment.IsDevelopment())\n    {\n        spa.UseVueCli(npmScript: \"serve\"); // npm run server komutunu tetikler\n    }\n});\n\napp.Run();\n```\n\nŞu aşamada sistemi ayağa kaldırmak için WebApi projesindeyken dotnet run komutunu verebiliriz. \n\n## İpuçları\n\nEğer SPA'dan vazgeçilirse dotnet web api ve vue uygulamaları ayrı ayrı çalıştırılabilir.\n\n```bash\ndotnet run\n\nnpm run serve\n```\n\nVue tarafında oluşabilecek bazı formatsal hataları otomatik olarak ayıklamak için aşağıdaki komut kullanılabilir.\n\n```bash\nnpm run lint -- --fix\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fburaksenyurt%2Fguide-for-aca","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fburaksenyurt%2Fguide-for-aca","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fburaksenyurt%2Fguide-for-aca/lists"}