Skip to content

Commit abc2a01

Browse files
committed
Added RSS Feed to page
1 parent bf4f867 commit abc2a01

File tree

7 files changed

+140
-8
lines changed

7 files changed

+140
-8
lines changed

src/LinkDotNet.Blog.Infrastructure/LinkDotNet.Blog.Infrastructure.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.3" />
10+
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.4" />
1111
<PackageReference Include="RavenDB.Client" Version="5.3.102" />
12-
<PackageReference Include="X.PagedList" Version="8.1.0" />
12+
<PackageReference Include="X.PagedList" Version="8.4.0" />
1313
</ItemGroup>
1414

1515
<ItemGroup>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.ServiceModel.Syndication;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using System.Xml;
8+
using LinkDotNet.Blog.Domain;
9+
using LinkDotNet.Blog.Infrastructure.Persistence;
10+
using Microsoft.AspNetCore.Mvc;
11+
12+
namespace LinkDotNet.Blog.Web.Controller;
13+
14+
public class RssFeedController : ControllerBase
15+
{
16+
private readonly AppConfiguration appConfiguration;
17+
private readonly IRepository<BlogPost> blogPostRepository;
18+
19+
public RssFeedController(AppConfiguration appConfiguration, IRepository<BlogPost> blogPostRepository)
20+
{
21+
this.appConfiguration = appConfiguration;
22+
this.blogPostRepository = blogPostRepository;
23+
}
24+
25+
[ResponseCache(Duration = 1200)]
26+
[HttpGet]
27+
[Route("feed.rss")]
28+
public async Task<IActionResult> GetRssFeed()
29+
{
30+
var url = $"{Request.Scheme}://{Request.Host}{Request.PathBase}";
31+
var feed = new SyndicationFeed(appConfiguration.BlogName, appConfiguration.Introduction?.Description, new Uri(url))
32+
{
33+
Items = await GetBlogPostItems(url),
34+
};
35+
36+
var settings = new XmlWriterSettings
37+
{
38+
Encoding = Encoding.UTF8,
39+
NewLineHandling = NewLineHandling.Entitize,
40+
NewLineOnAttributes = true,
41+
Indent = true,
42+
Async = true,
43+
};
44+
45+
using var stream = new MemoryStream();
46+
await using var xmlWriter = XmlWriter.Create(stream, settings);
47+
var rssFormatter = new Rss20FeedFormatter(feed, false);
48+
rssFormatter.WriteTo(xmlWriter);
49+
await xmlWriter.FlushAsync();
50+
51+
return File(stream.ToArray(), "application/rss+xml; charset=utf-8");
52+
}
53+
54+
private async Task<List<SyndicationItem>> GetBlogPostItems(string url)
55+
{
56+
var blogPostItems = new List<SyndicationItem>();
57+
var blogPosts = await blogPostRepository.GetAllAsync(f => f.IsPublished, orderBy: post => post.UpdatedDate);
58+
foreach (var blogPost in blogPosts)
59+
{
60+
var blogPostUrl = url + $"/blogPost/{blogPost.Id}";
61+
var item = new SyndicationItem(blogPost.Title, blogPost.ShortDescription, new Uri(blogPostUrl), blogPost.Id, blogPost.UpdatedDate)
62+
{
63+
PublishDate = blogPost.UpdatedDate,
64+
LastUpdatedTime = blogPost.UpdatedDate,
65+
};
66+
blogPostItems.Add(item);
67+
}
68+
69+
return blogPostItems;
70+
}
71+
}

src/LinkDotNet.Blog.Web/Features/Home/Components/NavMenu.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<a class="nav-link" href="AboutMe"><i class="far fa-address-card"></i> About
3030
me</a></li>
3131
}
32+
<li class="nav-item active"><a class="nav-link" href="/feed.rss"><i class="fas fa-rss"></i> RSS</a></li>
3233

3334
<AccessControl CurrentUri="@currentUri"></AccessControl>
3435
<li class="nav-item d-flex">

src/LinkDotNet.Blog.Web/LinkDotNet.Blog.Web.csproj

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
<ItemGroup>
99
<PackageReference Include="Blazored.Toast" Version="3.2.2" />
1010
<PackageReference Include="BuildBundlerMinifier" Version="3.2.449" />
11-
<PackageReference Include="Markdig" Version="0.30.0" />
12-
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.3" />
13-
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.3" />
14-
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.3" />
11+
<PackageReference Include="Markdig" Version="0.30.2" />
12+
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.4" />
13+
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.4" />
14+
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.4" />
15+
<PackageReference Include="System.ServiceModel.Syndication" Version="6.0.0" />
1516
</ItemGroup>
1617

1718
<ItemGroup>

src/LinkDotNet.Blog.Web/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ private static void ConfigureApp(WebApplication app)
6767
app.UseAuthentication();
6868
app.UseAuthorization();
6969

70+
app.MapControllers();
7071
app.MapBlazorHub();
7172
app.MapFallbackToPage("/_Host");
7273
app.MapFallbackToPage("/searchByTag/{tag}", "/_Host");

tests/LinkDotNet.Blog.IntegrationTests/LinkDotNet.Blog.IntegrationTests.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
</PropertyGroup>
77

88
<ItemGroup>
9-
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.3" />
10-
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.3" />
9+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.4" />
10+
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.4" />
1111
<PackageReference Include="RavenDB.TestDriver" Version="5.3.102" />
1212
</ItemGroup>
1313

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
using System.Text;
3+
using System.Threading.Tasks;
4+
using LinkDotNet.Blog.Domain;
5+
using LinkDotNet.Blog.TestUtilities;
6+
using LinkDotNet.Blog.Web;
7+
using LinkDotNet.Blog.Web.Controller;
8+
using Microsoft.AspNetCore.Http;
9+
using Microsoft.AspNetCore.Mvc;
10+
11+
namespace LinkDotNet.Blog.IntegrationTests.Web.Controller;
12+
13+
public class RssFeedControllerTests : SqlDatabaseTestBase<BlogPost>
14+
{
15+
[Fact]
16+
public async Task ShouldCreateRssFeed()
17+
{
18+
var request = new Mock<HttpRequest>();
19+
request.Setup(x => x.Scheme).Returns("http");
20+
request.Setup(x => x.Host).Returns(new HostString("localhost"));
21+
request.Setup(x => x.PathBase).Returns(PathString.FromUriComponent("/"));
22+
var httpContext = Mock.Of<HttpContext>(_ => _.Request == request.Object);
23+
var controllerContext = new ControllerContext
24+
{
25+
HttpContext = httpContext,
26+
};
27+
var config = new AppConfiguration
28+
{
29+
BlogName = "Test",
30+
Introduction = new Introduction
31+
{
32+
Description = "Description",
33+
},
34+
};
35+
var blogPost1 = new BlogPostBuilder()
36+
.WithTitle("1")
37+
.WithShortDescription("Short 1")
38+
.WithUpdatedDate(new DateTime(2022, 5, 1))
39+
.Build();
40+
var blogPost2 = new BlogPostBuilder()
41+
.WithTitle("2")
42+
.WithShortDescription("Short 2")
43+
.WithUpdatedDate(new DateTime(2022, 6, 1))
44+
.Build();
45+
await Repository.StoreAsync(blogPost1);
46+
await Repository.StoreAsync(blogPost2);
47+
var cut = new RssFeedController(config, Repository)
48+
{
49+
ControllerContext = controllerContext,
50+
};
51+
52+
var xml = await cut.GetRssFeed() as FileContentResult;
53+
54+
xml.Should().NotBeNull();
55+
var content = Encoding.UTF8.GetString(xml.FileContents);
56+
content.Should().NotBeNull();
57+
}
58+
}

0 commit comments

Comments
 (0)