Skip to content

Refactor HTML generation into shared base handler.#182

Merged
supermartzin merged 2 commits intomainfrom
issues/181-html-handlers-common-base
Jan 13, 2026
Merged

Refactor HTML generation into shared base handler.#182
supermartzin merged 2 commits intomainfrom
issues/181-html-handlers-common-base

Conversation

@supermartzin
Copy link
Owner

Extracted common HTML template and post rendering logic into a new HtmlBasedAdPostsHandlerBase class and project. Updated Email and File handlers to inherit from this base, removing duplicated code. Adjusted project references and solution structure accordingly.

Extracted common HTML template and post rendering logic into a new HtmlBasedAdPostsHandlerBase class and project. Updated Email and File handlers to inherit from this base, removing duplicated code. Adjusted project references and solution structure accordingly.
@supermartzin supermartzin added this to the v2.0 milestone Jan 13, 2026
@supermartzin supermartzin self-assigned this Jan 13, 2026
@supermartzin supermartzin added 📈 enhancement New feature or request 👌 code quality Code quality improvement labels Jan 13, 2026
@gemini-code-assist
Copy link

Summary of Changes

Hello @supermartzin, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the maintainability and organization of the application's HTML generation capabilities. By extracting the common HTML templating and post-rendering logic into a new HtmlBasedAdPostsHandlerBase class, the PR eliminates duplicated code from the Email and File handlers. This refactoring centralizes the responsibility for HTML creation, making future modifications and extensions to the HTML output much simpler and less error-prone.

Highlights

  • New Base Handler: A new abstract base class, HtmlBasedAdPostsHandlerBase, has been introduced to centralize common HTML generation logic.
  • Common HTML Templates: All shared HTML template elements (full page, titles, and individual post structures) have been moved into a new static class, CommonHtmlTemplateElements.
  • Code Duplication Reduction: The EmailNotifyingAdPostsHandler and LocalFileAdPostsHandler now inherit from the new base class, eliminating significant code duplication related to HTML rendering.
  • Project Structure Update: A new project (RealEstatesWatcher.AdPostsHandlers.Base.Html) was added, and existing projects were updated to reference it, while the old RealEstatesWatcher.AdPostsHandlers.Templates project was removed.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request successfully refactors the HTML generation logic into a shared base class, HtmlBasedAdPostsHandlerBase, which is a great improvement for code reuse and maintainability. The Email and File handlers are updated to use this new base class, removing significant code duplication.

My review includes a few suggestions to improve the implementation:

  • A critical fix for an invalid Target Framework in the project files.
  • Two high-severity bug fixes related to incorrect logic in the email and file handlers.
  • Some medium-severity suggestions for performance, correctness, and code style in the new base class.

Overall, this is a solid refactoring. Addressing the identified issues will make it even better.

logger?.LogDebug("Received new Real Estate Ad Post: {Post}", adPost);

await SendEmailAsync("🆕 New Real Estate Advert published!", CreateHtmlBody(adPost, CommonHtmlTemplateElements.TitleNewPosts), cancellationToken).ConfigureAwait(false);
await SendEmailAsync("🆕 New Real Estate Advert published!", CreateFullHtmlPage([adPost], CommonHtmlTemplateElements.TitleInitialPosts), cancellationToken).ConfigureAwait(false);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

For a single new ad post notification, you are using TitleInitialPosts which has the text 'Current Real estate offer'. You should use TitleNewPosts which has the text 'NEW Real estate offer' to be consistent with the email subject and the event.

        await SendEmailAsync("🆕 New Real Estate Advert published!", CreateFullHtmlPage([adPost], CommonHtmlTemplateElements.TitleNewPosts), cancellationToken).ConfigureAwait(false);

pageContent = pageContent.Insert(index + 8, htmlPostsElements + Environment.NewLine);
}
// write to file, keeping the <posts/> element for future appends
pageContent = pageContent.Insert(index + 8, htmlPostsElements + Environment.NewLine);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The comment indicates you intend to keep the <posts/> element for future appends, but the insertion logic is incorrect. pageContent.Insert(index + 8, ...) inserts the new posts after the <posts/> tag. To prepend the new posts and keep the tag for the next run, you should insert at index.

        pageContent = pageContent.Insert(index, htmlPostsElements + Environment.NewLine);

Comment on lines 22 to 23
postHtmlBuilder = postHtmlBuilder.Replace("{$address-links-display}", "inline-block")
.Replace("{$address-encoded}", HttpUtility.UrlEncode(post.Address));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The assignment postHtmlBuilder = postHtmlBuilder.Replace(...) is redundant because StringBuilder.Replace modifies the instance in-place and returns this. You can simply chain the calls without the assignment. This applies to the entire method where this pattern is used.

            postHtmlBuilder.Replace("{$address-links-display}", "inline-block")
                               .Replace("{$address-encoded}", HttpUtility.UrlEncode(post.Address));

// price
if (post.Price is not decimal.Zero)
{
postHtmlBuilder = postHtmlBuilder.Replace("{$price}", post.Price.ToString("N", new NumberFormatInfo { NumberGroupSeparator = " " }))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Creating new NumberFormatInfo on each call is inefficient. This object should be created once as a static readonly field and reused. This will improve performance, especially when processing a large number of ad posts. This also applies to formatting AdditionalFees on line 69.

You can add a field to the class:

private static readonly NumberFormatInfo SpaceSeparatedNumberFormat = new() { NumberGroupSeparator = " " };

And then use it here: post.Price.ToString("N", SpaceSeparatedNumberFormat)

Comment on lines 81 to 82
postHtmlBuilder = postHtmlBuilder.Replace("{$text}", post.Text)
.Replace("{$text-display}", "table");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using display: table for a <p> element is unconventional and can lead to unexpected layout behavior. The standard and more predictable value for a paragraph is display: block. Unless there's a specific reason for using table, it's better to change it.

            postHtmlBuilder = postHtmlBuilder.Replace("{$text}", post.Text)
                               .Replace("{$text-display}", "block");

Refactored HtmlBasedAdPostsHandlerBase and its derived handlers to accept a NumberFormatInfo parameter for consistent number formatting. Updated EmailNotifyingAdPostsHandler and LocalFileAdPostsHandler to use this parameter, and modified ConsoleRunner to register handlers with a space-separated number format. This improves localization and formatting consistency for numeric values in generated HTML.
@sonarqubecloud
Copy link

@supermartzin
Copy link
Owner Author

/gemini review

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively refactors the HTML generation logic into a shared base class, HtmlBasedAdPostsHandlerBase, which significantly reduces code duplication in EmailNotifyingAdPostsHandler and LocalFileAdPostsHandler. The introduction of the RealEstatesWatcher.AdPostsHandlers.Base.Html project is a good structural improvement.

My review includes a few points:

  • A critical issue with an invalid TargetFramework in the project files.
  • A high-severity bug where a template placeholder is not replaced, leading to broken links.
  • A medium-severity suggestion to improve the generated HTML's CSS for better predictability.

Overall, this is a solid refactoring. Once the identified issues are addressed, the code will be much cleaner and more maintainable.

@supermartzin supermartzin merged commit 0bfcc6f into main Jan 13, 2026
10 checks passed
@supermartzin supermartzin deleted the issues/181-html-handlers-common-base branch January 13, 2026 23:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

👌 code quality Code quality improvement 📈 enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove duplicated code and move common logic from Email and File Handlers to common base class.

1 participant