返回文章列表文章索引阅读全文
STARBLOG / 阅读手记
StarBlog博客开发笔记

Developing a Blog Project with .NetCore, StarBlog - (28) Developing APIs for Blogroll Links

The previously introduced blogroll feature only implemented the display and management interfaces for blogrolls. What's still missing are the blogroll application, review management, and notifications—this time, we'll complete this part. As for the Model and such, everything was covered in the previous article, so this one will directly focus on completing the logic code~

曦远 (DealiAxy)约 36 分钟
目录5 个章节

Preface

The previously introduced friend links feature only implemented the display and management interfaces for friend links.

It was still missing the friend link application, review management, and notifications. Now, this part of the functionality is being completed.

The Models and other aspects were covered in the previous article, so this one will directly complete the logical code~

For details, see: Developing a Blog Project with .NetCore StarBlog - (13) Adding Friend Links Functionality

Preview the Effect

Friendship Link Application Page

image-20230527231758438

Email Notification

Implement a simple notification feature that sends an email to the applicant's email address after a friend link application is approved.

Using the MimeKit library, you can conveniently implement email sending functionality.

For easier use, I encapsulated an EmailUtils class and placed it in StarBlog.Share.Utils.

public class EmailAccountConfig {
  public string Host { get; set; }
  public int Port { get; set; }
  public string FromUsername { get; set; }
  public string FromPassword { get; set; }
  public string FromAddress { get; set; }
}

public static class EmailUtils {
  public static async Task<MessageSentEventArgs> SendEmailAsync(
    EmailAccountConfig config,
    string subject,
    string htmlBody,
    string toName,
    string toAddress,
    string fromName = "StarBlog"
  ) {
    return await SendEmailAsync(
      config,
      new MimeMessage {
        Subject = subject,
        From = {new MailboxAddress(fromName, config.FromAddress)},
        To = {new MailboxAddress(toName, toAddress)},
        Body = new BodyBuilder {
          HtmlBody = htmlBody
        }.ToMessageBody()
      }
    );
  }

  public static async Task<MessageSentEventArgs> SendEmailAsync(EmailAccountConfig config, MimeMessage message,
                                                                HttpProxyClient? proxyClient = null) {
    MessageSentEventArgs result = null;
    using var client = new SmtpClient {
      ServerCertificateValidationCallback = (s, c, h, e) => true,
    };
    if (proxyClient != null) {
      client.ProxyClient = proxyClient;
    }

    client.AuthenticationMechanisms.Remove("XOAUTH2");
    client.MessageSent += (sender, args) => { result = args; };

    await client.ConnectAsync(config.Host, config.Port, SecureSocketOptions.Auto);
    await client.AuthenticateAsync(config.FromUsername, config.FromPassword);
    await client.SendAsync(message);
    await client.DisconnectAsync(true);

    return result;
  }
}

It's straightforward to use: simply pass in the email configuration, email subject, content, and recipient address.

For specific details, you can continue looking at the code below.

The logic for managing friend link application records also includes CRUD operations. The code for this part is the same as above, so it is omitted here.

The constructor uses dependency injection to read email configuration from the configuration system. You can add the email configuration to appsettings.json yourself. Here is an example configuration for an Outlook mailbox:

"EmailAccountConfig": {
  "Host": "smtp-mail.outlook.com",
  "Port": 587,
  "FromUsername": "邮箱地址@outlook.com",
  "FromPassword": "邮箱密码",
  "FromAddress": "邮箱地址@outlook.com"
}

Below is the code for the service.

Here, only the code for setting whether to verify and sending email notifications is provided.

public class LinkExchangeService {
  private readonly IBaseRepository<LinkExchange> _repo;
  private readonly LinkService _linkService;
  private readonly EmailAccountConfig _emailAccountConfig;

  public LinkExchangeService(IBaseRepository<LinkExchange> repo, LinkService linkService, IOptions<EmailAccountConfig> options) {
    _repo = repo;
    _linkService = linkService;
    _emailAccountConfig = options.Value;
  }

  public async Task<LinkExchange?> SetVerifyStatus(int id, bool status, string? reason = null) {
    var item = await GetById(id);
    if (item == null) return null;

    item.Verified = status;
    item.Reason = reason;
    await _repo.UpdateAsync(item);


    var link = await _linkService.GetByName(item.Name);
    if (status) {
      await SendEmailOnAccept(item);
      if (link == null) {
        await _linkService.AddOrUpdate(new Link {
          Name = item.Name,
          Description = item.Description,
          Url = item.Url,
          Visible = true
        });
      }
      else {
        await _linkService.SetVisibility(link.Id, true);
      }
    }
    else {
      await SendEmailOnReject(item);
      if (link != null) await _linkService.DeleteById(link.Id);
    }

    return await GetById(id);
  }

  // This article only includes the code for acceptance; other scenarios are similar
  public async Task SendEmailOnAccept(LinkExchange item) {
    const string starblogLink = "<a href=\"https://deali.cn\">StarBlog</a>";
    var sb = new StringBuilder();
    sb.AppendLine($"<p>Hello, your friend link application has been approved! Thank you for your support. Welcome to visit each other's sites~</p>");
    sb.AppendLine($"<br>");
    sb.AppendLine($"<p>Below is the information for your friend link application:</p>");
    sb.AppendLine($"<p>Website Name: {item.Name}</p>");
    sb.AppendLine($"<p>Description: {item.Description}</p>");
    sb.AppendLine($"<p>URL: {item.Url}</p>");
    sb.AppendLine($"<p>Webmaster: {item.WebMaster}</p>");
    sb.AppendLine($"<p>Additional Information: {item.Reason}</p>");
    sb.AppendLine($"<br>");
    sb.AppendLine($"<br>");
    sb.AppendLine($"<br>");
    sb.AppendLine($"<p>This message was automatically sent by {starblogLink}. No reply is required.</p>");
    await EmailUtils.SendEmailAsync(
      _emailAccountConfig,
      "[StarBlog] Friend Link Application Result Feedback",
      sb.ToString(),
      item.WebMaster,
      item.Email
    );
  }
}

In the method for setting verification status, the following actions are implemented:

  • Setting an application to verified automatically adds the link from that application to the friend links.
  • Setting an application to unverified automatically deletes the corresponding friend link (if it exists).

The rest is similar to the friend links feature mentioned above.

After completing the code, don't forget to register the services.

builder.Services.AddScoped<LinkExchangeService>();
builder.Services.AddScoped<LinkService>();

Blogroll Application

The display feature is complete; next, we need to develop the friend link application feature to make it easier for passing site admins to apply for link exchanges.

Add the StarBlog.Web/ViewModels/LinkExchange/LinkExchangeAddViewModel.cs file.

We use the form validation functionality provided by the AspNetCore MVC framework.

public class LinkExchangeAddViewModel {
  /// <summary>
  /// Website Name
  /// </summary>
  [Display(Name = "Website Name")]
  [Required(ErrorMessage = "The Website Name field is required.")]
  public string Name { get; set; }
}

/// <summary> /// Introduction /// </summary> [Display(Name = "Introduction")] public string? Description { get; set; }

/// <summary>
/// URL
/// </summary>
[Display(Name = "URL")]
[Required(ErrorMessage = "URL is required")]
[DataType(DataType.Url)]
public string Url { get; set; }

/// <summary> /// WebMaster /// </summary> [Display(Name = "WebMaster Name")] [Required(ErrorMessage = "WebMaster Name is required")] public string WebMaster { get; set; }

  /// <summary>
  /// Contact Email
  /// </summary>
  [Display(Name = "Contact Email")]
  [Required(ErrorMessage = "The Contact Email field is required")]
  [DataType(DataType.EmailAddress)]
  public string Email { get; set; }
}

Next, let's write the page StarBlog.Web/Views/LinkExchange/Add.cshtml

@model StarBlog.Web.ViewModels.LinkExchange.LinkExchangeAddViewModel
@{
ViewData["Title"] = "Apply for Friend Link (Link Exchange)";
}
<div class="container px-4 py-3"> <h2 class="d-flex w-100 justify-content-between pb-2 mb-3 border-bottom"> <div>Apply for Link Exchange</div> <div>Link Exchange</div> </h2> <div class="card px-1 py-3"> <form enctype="multipart/form-data" class="card-body row" asp-controller="LinkExchange" asp-action="Add" method="post"> <div class="col-xl-6"> <div class="mb-4"> <h4 class="card-title">Friend Link Information (友链信息)</h4> <h6 class="card-subtitle mb-3 text-muted">Please enter your website information for future contact.</h6> </div> <div class="mb-3"> <label asp-for="Name" class="form-label"></label> <input asp-for="Name" class="form-control"> <span asp-validation-for="Name" class="form-text text-danger"></span> </div> <div class="mb-3"> <label asp-for="Description" class="form-label"></label> <input asp-for="Description" class="form-control"> <span asp-validation-for="Description" class="form-text text-danger"></span> </div> <div class="mb-3"> <label asp-for="Url" class="form-label"></label> <input asp-for="Url" class="form-control"> <span asp-validation-for="Url" class="form-text text-danger"></span> </div> <div class="mb-3"> <label asp-for="WebMaster" class="form-label"></label> <input asp-for="WebMaster" class="form-control"> <span asp-validation-for="WebMaster" class="form-text text-danger"></span> </div> <div class="mb-3"> <label asp-for="Email" class="form-label"></label> <input asp-for="Email" class="form-control"> <span asp-validation-for="Email" class="form-text text-danger"></span> </div> </div> <div class="col-xl-6"> <div class="ms-3 mb-4"> <h4 class="card-title">Notes</h4> <h6 class="card-subtitle mb-3 text-muted">To apply for a friendly link, please ensure you meet the following requirements:</h6> </div> </div> <ul class="list-group list-group-flush list-group-numbered"> <li class="list-group-item d-flex justify-content-between align-items-start"> <div class="ms-2 me-auto"> <div class="fw-bold">Reciprocity</div> Please add a link to our site on your website first before applying for a friendship link exchange. </div> </li> <li class="list-group-item d-flex justify-content-between align-items-start"> <div class="ms-2 me-auto"> <div class="fw-bold">Content Category</div> Our site prioritizes similar original blogs or websites with related content. </div> </li> <li class="list-group-item d-flex justify-content-between align-items-start"> <div class="ms-2 me-auto"> <div class="fw-bold">SEO</div> Websites with proper indexing on Baidu and Google, and recent snapshots, are preferred. </div> </li> <li class="list-group-item d-flex justify-content-between align-items-start"> <div class="ms-2 me-auto"> <div class="fw-bold">Legality</div> Legitimate websites that do not contain content violating relevant national laws are required. Links from spam sites such as TB affiliate sites are not accepted. </div> </li> <li class="list-group-item d-flex justify-content-between align-items-start"> <div class="ms-2 me-auto"> <div class="fw-bold">Timeliness of Updates</div> Websites with very little original content and that have not been updated for a long time are not accepted. </div> </li> <li class="list-group-item d-flex justify-content-between align-items-start"> <div class="ms-2 me-auto"> <div class="fw-bold">Accessibility</div> If your website becomes inaccessible, the friendship link will be temporarily removed. Please contact the webmaster to restore it. </div> </li> </ul> </div> <div class="form-group"> <button type="submit" class="btn btn-outline-primary">Submit</button> <button type="reset" class="btn btn-outline-warning">Reset</button> </div> </form> </div> </div> ```

Lastly, add the Controller by creating the file StarBlog.Web/Controllers/LinkExchangeController.cs.

The code is as follows

public class LinkExchangeController : Controller {
  private readonly ILogger<LinkExchangeController> _logger;
  private readonly LinkExchangeService _service;
  private readonly IMapper _mapper;
  private readonly Messages _messages;
  public LinkExchangeController(
    ILogger<LinkExchangeController> logger, LinkExchangeService service, IMapper mapper,
    Messages messages) {
    _logger = logger;
    _service = service;
    _mapper = mapper;
    _messages = messages;
  }

The content you provided is entirely code with no Chinese text to translate. Per the rules, content inside code blocks should not be translated.

  [HttpGet]
  public IActionResult Add() {
    return View();
  }

[HttpPost] public async Task<IActionResult> Add(LinkExchangeAddViewModel vm) { if (!ModelState.IsValid) return View();

if (await _service.HasUrl(vm.Url)) { _messages.Error("A friend link application with the same URL has already been submitted!"); return View(); }

var item = _mapper.Map<LinkExchange>(vm);
item = await _service.AddOrUpdate(item);
    // Send email notification
    await _service.SendEmailOnAdd(item);

_messages.Info("Friend link request has been submitted and is being processed. Please check your email promptly for updates~"); return RedirectToAction("Index", "Home"); } }


Got it~
## Everything is Ready

Welcome, Webmaster Gurus, to exchange friend links~!

_EOF_

Developing a Blog Project with .NetCore, StarBlog - (28) Developing APIs for Blogroll Links

本文作者:
程序设计实验室
版权声明:
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关文章

StarBlog博客开发笔记(18):实现本地Typora文章打包上传

前言 九月太忙,只更新了三篇文章,本来这个功能是从九月初就开始做的,结果一直拖到现在国庆假期才有时间完善并且写文章~ 之前我更新了几篇关于 Python 的文章,有朋友留言问是不是不更新 .Net 了,那肯定不能啊,我只能说**「我 全 都 要」**,所以我反手就更新了一篇Asp-Net-Core开发笔记。 然后顺便立个Flag:今年底前完成StarBlog系列文章的主体部分(即API开发+后台前

2022-10-06 · StarBlog博客开发笔记 · 约 1 分钟

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…

Developing a Blog Project with .NetCore, StarBlog - (28) Developing APIs for Blogroll Links · StarBlog