diff --git a/BadNews/Components/ArchiveLinksViewComponent.cs b/BadNews/Components/ArchiveLinksViewComponent.cs new file mode 100644 index 0000000..731ccae --- /dev/null +++ b/BadNews/Components/ArchiveLinksViewComponent.cs @@ -0,0 +1,37 @@ +using System; +using BadNews.Repositories.News; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Caching.Memory; + +namespace BadNews.Components +{ + public class ArchiveLinksViewComponent : ViewComponent + { + private readonly INewsRepository newsRepository; + private readonly IMemoryCache memoryCache; + + public ArchiveLinksViewComponent(INewsRepository newsRepository, IMemoryCache memoryCache) + { + this.newsRepository = newsRepository; + this.memoryCache = memoryCache; + } + + public IViewComponentResult Invoke() + { + var cacheKey = nameof(ArchiveLinksViewComponent); + if (!memoryCache.TryGetValue(cacheKey, out var years)) + { + years = newsRepository.GetYearsWithArticles(); + if (years != null) + { + memoryCache.Set(cacheKey, years, new MemoryCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30) + }); + } + } + years = newsRepository.GetYearsWithArticles(); + return View(years); + } + } +} \ No newline at end of file diff --git a/BadNews/Components/WeatherViewComponent.cs b/BadNews/Components/WeatherViewComponent.cs new file mode 100644 index 0000000..f463adb --- /dev/null +++ b/BadNews/Components/WeatherViewComponent.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using BadNews.Repositories.Weather; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; + +namespace BadNews.Components +{ + public class WeatherViewComponent : ViewComponent + { + private IWeatherForecastRepository weatherForecastRepository; + + public WeatherViewComponent(IWeatherForecastRepository weatherForecastRepository) + { + this.weatherForecastRepository = weatherForecastRepository; + } + + public async Task InvokeAsync() + { + var data = await weatherForecastRepository.GetWeatherForecastAsync(); + return View(data); + } + } +} \ No newline at end of file diff --git a/BadNews/Controllers/EditorController.cs b/BadNews/Controllers/EditorController.cs new file mode 100644 index 0000000..980ec96 --- /dev/null +++ b/BadNews/Controllers/EditorController.cs @@ -0,0 +1,62 @@ +using System; +using BadNews.Elevation; +using BadNews.Models.Editor; +using BadNews.Repositories.News; +using Microsoft.AspNetCore.Mvc; + +namespace BadNews.Controllers +{ + [ElevationRequiredFilter] + public class EditorController : Controller + { + private readonly INewsRepository newsRepository; + + public EditorController(INewsRepository newsRepository) + { + this.newsRepository = newsRepository; + } + + public IActionResult Index() + { + return View(new IndexViewModel()); + } + + [HttpPost] + public IActionResult CreateArticle([FromForm] IndexViewModel model) + { + if (!ModelState.IsValid) + return View("Index", model); + var id = newsRepository.CreateArticle(new NewsArticle { + Date = DateTime.Now.Date, + Header = model.Header, + Teaser = model.Teaser, + ContentHtml = model.ContentHtml, + }); + + return RedirectToAction("FullArticle", "News", new { + id = id + }); + } + + [HttpPost] + public IActionResult DeleteArticle(Guid id) + { + newsRepository.DeleteArticleById(id); + return RedirectToAction("Index", "News"); + } + } + + // public class IndexViewModel + // { + // [Required(ErrorMessage = "У новости должен быть заголовок")] + // public string Header { get; set; } + // + // [StopWords("действительно", "реально", "на самом деле", "поверьте", "без обмана", + // ErrorMessage = "Нельзя использовать стоп-слова")] + // public string Teaser { get; set; } + // + // [StopWords("действительно", "реально", "на самом деле", "поверьте", "без обмана", + // ErrorMessage = "Нельзя использовать стоп-слова")] + // public string ContentHtml { get; set; } + // } +} diff --git a/BadNews/Controllers/ErrorsController.cs b/BadNews/Controllers/ErrorsController.cs new file mode 100644 index 0000000..53489fe --- /dev/null +++ b/BadNews/Controllers/ErrorsController.cs @@ -0,0 +1,27 @@ +using System; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace BadNews.Controllers +{ + public class ErrorsController : Controller + { + private readonly ILogger logger; + + public ErrorsController(ILogger logger) + { + this.logger = logger; + } + + public IActionResult StatusCode(int? code) + { + logger.LogWarning("status-code {code} at {time}", code, DateTime.Now); + return View(code); + } + + public IActionResult Exception() + { + return View(null, HttpContext.TraceIdentifier); + } + } +} \ No newline at end of file diff --git a/BadNews/Controllers/NewsController.cs b/BadNews/Controllers/NewsController.cs new file mode 100644 index 0000000..c686784 --- /dev/null +++ b/BadNews/Controllers/NewsController.cs @@ -0,0 +1,49 @@ +using System; +using BadNews.ModelBuilders.News; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Caching.Memory; + +namespace BadNews.Controllers +{ + [ResponseCache(Duration = 30, Location = ResponseCacheLocation.Client, VaryByHeader = "Cookie")] + public class NewsController : Controller + { + private readonly INewsModelBuilder newsModelBuilder; + private readonly IMemoryCache memoryCache; + + public NewsController(INewsModelBuilder newsModelBuilder, IMemoryCache memoryCache) + { + this.newsModelBuilder = newsModelBuilder; + this.memoryCache = memoryCache; + } + + public IActionResult Index(int? year) + { + var pageIndex = 0; + var model = newsModelBuilder.BuildIndexModel(pageIndex, true, year); + return View(model); + } + + [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult FullArticle(Guid id) + { + if (memoryCache.TryGetValue(id, out var model)) return View(model); + model = newsModelBuilder.BuildFullArticleModel(id); + if (model != null) + { + memoryCache.Set(id, model, new MemoryCacheEntryOptions + { + SlidingExpiration = TimeSpan.FromSeconds(10) + }); + } + + else + { + return NotFound(); + } + + + return View(model); + } + } +} \ No newline at end of file diff --git a/BadNews/Elevation/ElevationExtensions.cs b/BadNews/Elevation/ElevationExtensions.cs index eed04db..e8f0c73 100644 --- a/BadNews/Elevation/ElevationExtensions.cs +++ b/BadNews/Elevation/ElevationExtensions.cs @@ -7,8 +7,8 @@ public static class ElevationExtensions { public static bool IsElevated(this HttpRequest request) { - bool isElevated = request.Cookies.TryGetValue(ElevationConstants.CookieName, out var value) - && value == ElevationConstants.CookieValue; + var isElevated = request.Cookies.TryGetValue(ElevationConstants.CookieName, out var value) + && value == ElevationConstants.CookieValue; return isElevated; } @@ -17,4 +17,4 @@ public static bool IsElevated(this ViewContext viewContext) return viewContext.HttpContext.Request.IsElevated(); } } -} +} \ No newline at end of file diff --git a/BadNews/Elevation/ElevationMiddleware.cs b/BadNews/Elevation/ElevationMiddleware.cs index 52da41e..aa8081a 100644 --- a/BadNews/Elevation/ElevationMiddleware.cs +++ b/BadNews/Elevation/ElevationMiddleware.cs @@ -7,15 +7,34 @@ namespace BadNews.Elevation public class ElevationMiddleware { private readonly RequestDelegate next; - + public ElevationMiddleware(RequestDelegate next) { this.next = next; } - + public async Task InvokeAsync(HttpContext context) { - throw new NotImplementedException(); + if (!context.Request.Path.Equals("/elevation")) + { + await next(context); + return; + } + + if (context.Request.Query.ContainsKey("up")) + { + context.Response.Cookies.Append(ElevationConstants.CookieName, ElevationConstants.CookieValue, + new CookieOptions + { + HttpOnly = true + }); + } + else + { + context.Response.Cookies.Delete(ElevationConstants.CookieName); + } + + context.Response.Redirect("/"); } } -} +} \ No newline at end of file diff --git a/BadNews/Program.cs b/BadNews/Program.cs index c2036c8..7c5e6b9 100644 --- a/BadNews/Program.cs +++ b/BadNews/Program.cs @@ -1,7 +1,10 @@ +using System; using System.Linq; using BadNews.Repositories.News; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; +using Serilog; namespace BadNews { @@ -10,7 +13,31 @@ public class Program public static void Main(string[] args) { InitializeDataBase(); - CreateHostBuilder(args).Build().Run(); + + Log.Logger = new LoggerConfiguration() + .WriteTo.File(".logs/start-host-log-.txt", + rollingInterval: RollingInterval.Day, + rollOnFileSizeLimit: true, + outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}") + .CreateLogger(); + + try + { + Log.Information("Creating web host builder"); + var hostBuilder = CreateHostBuilder(args); + Log.Information("Building web host"); + var host = hostBuilder.Build(); + Log.Information("Running web host"); + host.Run(); + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly"); + } + finally + { + Log.CloseAndFlush(); + } } public static IHostBuilder CreateHostBuilder(string[] args) @@ -19,12 +46,19 @@ public static IHostBuilder CreateHostBuilder(string[] args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); - }); + // webBuilder.UseEnvironment(Environments.Development); + }) + .ConfigureHostConfiguration(config => + { + config.AddJsonFile("appsettings.Secret.json", optional: true, reloadOnChange: false); + }) + .UseSerilog((hostingContext, loggerConfiguration) => + loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration)); } private static void InitializeDataBase() { - const int newsArticleCount = 100; + const int newsArticleCount = 1000; var generator = new NewsGenerator(); var articles = generator.GenerateNewsArticles() @@ -36,4 +70,4 @@ private static void InitializeDataBase() repository.InitializeDataBase(articles); } } -} +} \ No newline at end of file diff --git a/BadNews/Repositories/Weather/OpenWeatherForecast.cs b/BadNews/Repositories/Weather/OpenWeatherForecast.cs index 38c8532..ef73a56 100644 --- a/BadNews/Repositories/Weather/OpenWeatherForecast.cs +++ b/BadNews/Repositories/Weather/OpenWeatherForecast.cs @@ -18,12 +18,12 @@ public class WeatherInfo public class MainInfo { - public int Temp { get; set; } + public double Temp { get; set; } public decimal FeelsLike { get; set; } - public int TempMin { get; set; } - public int TempMax { get; set; } - public int Pressure { get; set; } - public int Humidity { get; set; } + public double TempMin { get; set; } + public double TempMax { get; set; } + public double Pressure { get; set; } + public double Humidity { get; set; } } } } diff --git a/BadNews/Repositories/Weather/WeatherForecast.cs b/BadNews/Repositories/Weather/WeatherForecast.cs index 4e67c55..4ba2473 100644 --- a/BadNews/Repositories/Weather/WeatherForecast.cs +++ b/BadNews/Repositories/Weather/WeatherForecast.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; namespace BadNews.Repositories.Weather @@ -13,7 +14,7 @@ public static WeatherForecast CreateFrom(OpenWeatherForecast forecast) { return new WeatherForecast { - TemperatureInCelsius = forecast.Main.Temp, + TemperatureInCelsius = (int)forecast.Main.Temp, IconUrl = forecast.Weather.FirstOrDefault()?.IconUrl ?? defaultWeatherImageUrl }; } diff --git a/BadNews/Repositories/Weather/WeatherForecastRepository.cs b/BadNews/Repositories/Weather/WeatherForecastRepository.cs index f8d4cd7..dd24b9e 100644 --- a/BadNews/Repositories/Weather/WeatherForecastRepository.cs +++ b/BadNews/Repositories/Weather/WeatherForecastRepository.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Microsoft.Extensions.Options; namespace BadNews.Repositories.Weather { @@ -8,10 +9,24 @@ public class WeatherForecastRepository : IWeatherForecastRepository private const string defaultWeatherImageUrl = "/images/cloudy.png"; private readonly Random random = new Random(); + private readonly OpenWeatherClient weatherClient; + + public WeatherForecastRepository(IOptions weatherOptions) + { + var apiKey = weatherOptions?.Value.ApiKey; + weatherClient = new OpenWeatherClient(apiKey); + } public async Task GetWeatherForecastAsync() { - return BuildRandomForecast(); + try + { + return WeatherForecast.CreateFrom(await weatherClient.GetWeatherFromApiAsync()); + } + catch (Exception) + { + return BuildRandomForecast(); + } } private WeatherForecast BuildRandomForecast() diff --git a/BadNews/Startup.cs b/BadNews/Startup.cs index cd346c3..cfa76f5 100644 --- a/BadNews/Startup.cs +++ b/BadNews/Startup.cs @@ -1,19 +1,17 @@ -using BadNews.ModelBuilders.News; -using BadNews.Models.News; +using System; +using BadNews.Elevation; +using BadNews.ModelBuilders.News; using BadNews.Repositories.News; +using BadNews.Repositories.Weather; +using BadNews.Validation; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.DataAnnotations; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using System; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Web; +using Serilog; namespace BadNews { @@ -34,107 +32,60 @@ public void ConfigureServices(IServiceCollection services) { services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddMemoryCache(); + services.Configure(configuration.GetSection("OpenWeather")); + services.AddResponseCompression(options => + { + options.EnableForHttps = true; + }); + var mvcBuilder = services.AddControllersWithViews(); + if (env.IsDevelopment()) + mvcBuilder.AddRazorRuntimeCompilation(); } // В этом методе конфигурируется последовательность обработки HTTP-запроса public void Configure(IApplicationBuilder app) { - app.UseDeveloperExceptionPage(); + if (env.IsDevelopment()) + app.UseDeveloperExceptionPage(); + else + app.UseExceptionHandler("/Errors/Exception"); app.UseHttpsRedirection(); - app.UseStaticFiles(); + app.UseResponseCompression(); + app.UseStaticFiles(new StaticFileOptions() + { + OnPrepareResponse = options => + { + options.Context.Response.GetTypedHeaders().CacheControl = + new Microsoft.Net.Http.Headers.CacheControlHeaderValue() + { + Public = false, + MaxAge = TimeSpan.FromHours(1) + }; + } + }); + app.UseSerilogRequestLogging(); + app.UseStatusCodePagesWithReExecute("/StatusCode/{0}"); + app.UseMiddleware(); - app.Map("/news", newsApp => + app.UseRouting(); + app.UseEndpoints(endpoints => { - newsApp.Map("/fullarticle", fullArticleApp => + endpoints.MapControllerRoute("status-code", "StatusCode/{code?}", new { - fullArticleApp.Run(RenderFullArticlePage); + controller = "Errors", + action = "StatusCode" }); - - newsApp.Run(RenderIndexPage); + endpoints.MapControllerRoute("default", "{controller=News}/{action=Index}/{id?}"); }); - - app.MapWhen(context => context.Request.Path == "/", rootPathApp => + app.MapWhen(context => context.Request.IsElevated(), branchApp => { - rootPathApp.Run(RenderIndexPage); + branchApp.UseDirectoryBrowser("/files"); }); // Остальные запросы — 404 Not Found } - - // Региональные настройки, которые используются при обработке запросов новостей. - private static CultureInfo culture = CultureInfo.CreateSpecificCulture("ru-ru"); - - private async Task RenderIndexPage(HttpContext context) - { - // Model Builder достается из DI-контейнера - var newsModelBuilder = context.RequestServices.GetRequiredService(); - - // Извлекаются входные параметры запроса - int.TryParse(context.Request.Query["pageIndex"], out var pageIndex); - - // Строится модель страницы - var model = newsModelBuilder.BuildIndexModel(pageIndex, false, null); - - // Строится HTML для модели - string pageHtml = BuildIndexPageHtml(model); - - // Результат записывается в ответ - await context.Response.WriteAsync(pageHtml); - } - - private static string BuildIndexPageHtml(IndexModel model) - { - var articlesBuilder = new StringBuilder(); - var articleTemplate = File.ReadAllText("./$Content/Templates/NewsArticle.hbs"); - foreach (var articleModel in model.PageArticles) - { - var articleHtml = articleTemplate - .Replace("{{header}}", articleModel.Header) - .Replace("{{date}}", articleModel.Date.ToString("d MMM yyyy", culture)) - .Replace("{{teaser}}", articleModel.Teaser) - .Replace("{{url}}", $"/news/fullarticle/{HttpUtility.UrlEncode(articleModel.Id.ToString())}"); - articlesBuilder.AppendLine(articleHtml); - } - - var pageTemplate = File.ReadAllText("./$Content/Templates/Index.hbs"); - var pageHtml = pageTemplate - .Replace("{{articles}}", articlesBuilder.ToString()) - .Replace("{{newerUrl}}", !model.IsFirst - ? $"/news?pageIndex={HttpUtility.UrlEncode((model.PageIndex - 1).ToString())}" - : "") - .Replace("{{olderUrl}}", !model.IsLast - ? $"/news?pageIndex={HttpUtility.UrlEncode((model.PageIndex + 1).ToString())}" - : ""); - return pageHtml; - } - - private async Task RenderFullArticlePage(HttpContext context) - { - // Model Builder достается из DI-контейнера - var newsModelBuilder = context.RequestServices.GetRequiredService(); - - // Извлекаются входные параметры запроса - var idString = context.Request.Path.Value.Split('/').ElementAtOrDefault(1); - Guid.TryParse(idString, out var id); - - // Строится модель страницы - var model = newsModelBuilder.BuildFullArticleModel(id); - - // Строится HTML для модели - string pageHtml = BuildFullArticlePageHtml(model); - - // Результат записывается в ответ - await context.Response.WriteAsync(pageHtml); - } - - private static string BuildFullArticlePageHtml(FullArticleModel model) - { - var pageTemplate = File.ReadAllText("./$Content/Templates/FullArticle.hbs"); - var pageHtml = pageTemplate - .Replace("{{header}}", model.Article.Header) - .Replace("{{date}}", model.Article.Date.ToString("d MMM yyyy", culture)) - .Replace("{{content}}", model.Article.ContentHtml); - return pageHtml; - } } -} +} \ No newline at end of file diff --git a/BadNews/Views/Editor/Index.cshtml b/BadNews/Views/Editor/Index.cshtml new file mode 100644 index 0000000..ea50143 --- /dev/null +++ b/BadNews/Views/Editor/Index.cshtml @@ -0,0 +1,43 @@ +@model BadNews.Models.Editor.IndexViewModel +
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/BadNews/Views/Errors/Exception.cshtml b/BadNews/Views/Errors/Exception.cshtml new file mode 100644 index 0000000..feaf1e8 --- /dev/null +++ b/BadNews/Views/Errors/Exception.cshtml @@ -0,0 +1,16 @@ +@model string + + + +
+

Во время обработки вашего запроса возникла ошибка

+ + @if (!string.IsNullOrEmpty(Model)) + { +

Request ID: @Model

+ } + +

Вернуться на главную страницу

+
+ + diff --git a/BadNews/Views/Errors/StatusCode.cshtml b/BadNews/Views/Errors/StatusCode.cshtml new file mode 100644 index 0000000..327f148 --- /dev/null +++ b/BadNews/Views/Errors/StatusCode.cshtml @@ -0,0 +1,32 @@ +@model int? +@{ + Layout = null; +} + + + + + + + + Bad News + + +
@(Model?.ToString() ?? "Неизвестный код состояния")
+ + + \ No newline at end of file diff --git a/BadNews/Views/News/FullArticle.cshtml b/BadNews/Views/News/FullArticle.cshtml new file mode 100644 index 0000000..f0e4ae6 --- /dev/null +++ b/BadNews/Views/News/FullArticle.cshtml @@ -0,0 +1,40 @@ +@using BadNews.Elevation +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using BadNews.Components +@model BadNews.Models.News.FullArticleModel + + + +
+
+
+
+

@Model.Article.Header

+ + @if (ViewContext.IsElevated()) + { +
+ +
+ } + @Html.Raw(@Model.Article.ContentHtml) +
+
+ + +
+
+ + + + \ No newline at end of file diff --git a/BadNews/Views/News/Index.cshtml b/BadNews/Views/News/Index.cshtml new file mode 100644 index 0000000..10c5f38 --- /dev/null +++ b/BadNews/Views/News/Index.cshtml @@ -0,0 +1,84 @@ +@model BadNews.Models.News.IndexModel +@using System.Web + +@section Header +{ +
+ @foreach (var article in Model.FeaturedArticles) + { +
+
+
+

@article.Header

+
@article.Date.ToString("yy MMM dd", ViewBag.Culture)
+

+ @article.Teaser +

+ Читать полностью +
+
+
+ } +
+ +} + + + +
+
+
+ @foreach (var article in Model.PageArticles) + { +
+

@article.Header

+ +

+ @article.Teaser +

+ Читать полностью +
+ } + + + +
+ + +
+
+ + + + \ No newline at end of file diff --git a/BadNews/Views/Shared/Components/ArchiveLinks/Default.cshtml b/BadNews/Views/Shared/Components/ArchiveLinks/Default.cshtml new file mode 100644 index 0000000..0c0d257 --- /dev/null +++ b/BadNews/Views/Shared/Components/ArchiveLinks/Default.cshtml @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/BadNews/Views/Shared/Components/Weather/Default.cshtml b/BadNews/Views/Shared/Components/Weather/Default.cshtml new file mode 100644 index 0000000..e230e8b --- /dev/null +++ b/BadNews/Views/Shared/Components/Weather/Default.cshtml @@ -0,0 +1,11 @@ +@model BadNews.Repositories.Weather.WeatherForecast +@{ + string FormatTemperature(int temperature) => temperature > 0 ? $"+{temperature}" : temperature.ToString(); +} +
+

Погода сейчас

+
+ +
+
@FormatTemperature(Model.TemperatureInCelsius)
+
\ No newline at end of file diff --git a/BadNews/Views/Shared/_Layout.cshtml b/BadNews/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000..8d366ca --- /dev/null +++ b/BadNews/Views/Shared/_Layout.cshtml @@ -0,0 +1,42 @@ +@using BadNews.Elevation +@using Microsoft.AspNetCore.Mvc.TagHelpers + + + + + + Bad News + + + + + + +
+
+ + @if (ViewContext.IsElevated()) + { + есть привилегии + } +
+ + + + @RenderSection("Header", false) +
+ + + +@RenderBody() + + + + + + + + + \ No newline at end of file diff --git a/BadNews/Views/Shared/_ValidationScriptsPartial.cshtml b/BadNews/Views/Shared/_ValidationScriptsPartial.cshtml new file mode 100644 index 0000000..a5ceffc --- /dev/null +++ b/BadNews/Views/Shared/_ValidationScriptsPartial.cshtml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/BadNews/Views/_ViewImports.cshtml b/BadNews/Views/_ViewImports.cshtml new file mode 100644 index 0000000..9a77ebc --- /dev/null +++ b/BadNews/Views/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, BadNews \ No newline at end of file diff --git a/BadNews/Views/_ViewStart.cshtml b/BadNews/Views/_ViewStart.cshtml new file mode 100644 index 0000000..6fedfde --- /dev/null +++ b/BadNews/Views/_ViewStart.cshtml @@ -0,0 +1,5 @@ +@using System.Globalization +@{ + ViewBag.Culture = CultureInfo.CreateSpecificCulture("ru-ru"); + Layout = "_Layout"; +} \ No newline at end of file diff --git a/BadNews/appsettings.Development.json b/BadNews/appsettings.Development.json index 8983e0f..077404a 100644 --- a/BadNews/appsettings.Development.json +++ b/BadNews/appsettings.Development.json @@ -1,9 +1,3 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} + +} \ No newline at end of file diff --git a/BadNews/appsettings.json b/BadNews/appsettings.json index d9d9a9b..6f5ed00 100644 --- a/BadNews/appsettings.json +++ b/BadNews/appsettings.json @@ -1,10 +1,29 @@ { - "Logging": { - "LogLevel": { + "Serilog": { + "WriteTo": [ + { + "Name": "File", + "Args": { + "path": ".logs/log-.txt", + "formatter": "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact", + "rollingInterval": "Day", + "rollOnFileSizeLimit": true + } + }, + { + "Name": "Console", + "Args": { + "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}" + } + } + ], + "MinimumLevel": { "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" + "Override": { + "Microsoft": "Warning", + "System": "Warning" + } } }, "AllowedHosts": "*" -} +} \ No newline at end of file