Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ФТ-301: Толченов - Суровнев - Коуров - Шишкин #19

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions BadNews/Components/ArchiveLinksViewComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using BadNews.Repositories.News;
using Microsoft.AspNetCore.Mvc;

namespace BadNews.Components
{
public class ArchiveLinksViewComponent : ViewComponent
{
private INewsRepository NewsRepository { get; }
public ArchiveLinksViewComponent(INewsRepository newsRepository)
{
NewsRepository = newsRepository;
}

public IViewComponentResult Invoke() => View(NewsRepository.GetYearsWithArticles());
}
}
39 changes: 39 additions & 0 deletions BadNews/Controllers/EditorController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using BadNews.Models.Editor;
using BadNews.Repositories.News;
using Microsoft.AspNetCore.Mvc;

namespace BadNews.Controllers
{
public class EditorController : Controller
{
private INewsRepository NewsRepository { get; }

public EditorController(INewsRepository newsRepository)
{
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 });
}
}
}
24 changes: 24 additions & 0 deletions BadNews/Controllers/ErrorsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace BadNews.Controllers
{
public class ErrorsController : Controller
{
private ILogger<ErrorsController> Logger { get; init; }
public ErrorsController(ILogger<ErrorsController> logger)
{
Logger = logger;
}
public IActionResult Exception()
{
return View(null, HttpContext.TraceIdentifier);
}
public IActionResult StatusCode(int? code)
{
Logger.LogWarning("status-code {code} at {time}", code, DateTime.Now);
return View(code);
}
}
}
29 changes: 29 additions & 0 deletions BadNews/Controllers/NewsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using BadNews.ModelBuilders.News;
using Microsoft.AspNetCore.Mvc;

namespace BadNews.Controllers
{
public class NewsController : Controller
{
private readonly INewsModelBuilder newsModelBuilder;

public NewsController(INewsModelBuilder newsModelBuilder)
{
this.newsModelBuilder = newsModelBuilder;
}

public IActionResult Index(int? year = null, int pageIndex = 0)
{
var model = newsModelBuilder.BuildIndexModel(pageIndex, true, year);
return View(model);
}
public IActionResult FullArticle(Guid id)
{
var model = newsModelBuilder.BuildFullArticleModel(id);
if (model == null)
return NotFound();
return View(model);
}
}
}
31 changes: 29 additions & 2 deletions BadNews/Program.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Linq;
using BadNews.Repositories.News;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Serilog;

namespace BadNews
{
Expand All @@ -10,7 +12,30 @@ 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)
Expand All @@ -19,7 +44,9 @@ public static IHostBuilder CreateHostBuilder(string[] args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
webBuilder.UseEnvironment(Environments.Development);
})
.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration));
}

private static void InitializeDataBase()
Expand Down
2 changes: 1 addition & 1 deletion BadNews/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
"ASPNETCORE_ENVIRONMENT": "Production"
}
}
}
Expand Down
106 changes: 17 additions & 89 deletions BadNews/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
using System.Text;
using System.Threading.Tasks;
using System.Web;
using BadNews.Validation;
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Serilog;

namespace BadNews
{
Expand All @@ -34,107 +37,32 @@ public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<INewsRepository, NewsRepository>();
services.AddSingleton<INewsModelBuilder, NewsModelBuilder>();
services.AddSingleton<IValidationAttributeAdapterProvider, StopWordsAttributeAdapterProvider>();
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.UseSerilogRequestLogging();
app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");

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);
});

app.MapWhen(context => context.Request.Path == "/", rootPathApp =>
{
rootPathApp.Run(RenderIndexPage);
endpoints.MapControllerRoute("default", "{controller=News}/{action=Index}/{id?}");
});

// Остальные запросы — 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<INewsModelBuilder>();

// Извлекаются входные параметры запроса
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<INewsModelBuilder>();

// Извлекаются входные параметры запроса
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;
}
}
}
43 changes: 43 additions & 0 deletions BadNews/Views/Editor/Index.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@model BadNews.Models.Editor.IndexViewModel
<main role="main" class="container">
<div class="row">
<div class="m-auto">
<form asp-controller="Editor" asp-action="CreateArticle">
<div class="p-2">
<div>
<label>Заголовок*</label>
</div>
<div>
<input asp-for="Header" type="text" size="80">
</div>
<div>
<span asp-validation-for="Header" class="text-danger"></span>
</div>
</div>
<div class="p-2">
<div class="mr-2">
<label>Тизер</label>
</div>
<div>
<textarea asp-for="Teaser" cols="80" rows="5"></textarea>
</div>
<div>
<span asp-validation-for="Teaser" class="text-danger"></span>
</div>
</div>
<div class="p-2">
<div class="mr-2"><label>Текст статьи в HTML</label></div>
<div>
<textarea asp-for="ContentHtml" cols="80" rows="15"></textarea>
</div>
<div>
<span asp-validation-for="ContentHtml" class="text-danger"></span>
</div>
</div>
<div class="p-2 mb-4 text-center">
<button class="btn btn-primary" type="submit">Отправить</button>
</div>
</form>
</div>
</div>
</main>
12 changes: 12 additions & 0 deletions BadNews/Views/Errors/Exception.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@model string

<main role="main" class="container">
<h3 class="text-center text-danger">Во время обработки вашего запроса возникла ошибка</h3>

@if (!string.IsNullOrEmpty(Model))
{
<p class="text-center"><strong>Request ID:</strong> <code>@Model</code></p>
}

<p class="text-center"><a href="/">Вернуться на главную страницу</a></p>
</main>
Loading