系列文章

  1. 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目
  2. 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来
  3. 基于 abp vNext 和 .NET Core 开发博客项目 - 完善与美化,Swagger登场
  4. 基于 abp vNext 和 .NET Core 开发博客项目 - 数据访问和代码优先
  5. 基于 abp vNext 和 .NET Core 开发博客项目 - 自定义仓储之增删改查
  6. 基于 abp vNext 和 .NET Core 开发博客项目 - 统一规范API,包装返回模型
  7. 基于 abp vNext 和 .NET Core 开发博客项目 - 再说Swagger,分组、描述、小绿锁
  8. 基于 abp vNext 和 .NET Core 开发博客项目 - 接入GitHub,用JWT保护你的API
  9. 基于 abp vNext 和 .NET Core 开发博客项目 - 异常处理和日志记录
  10. 基于 abp vNext 和 .NET Core 开发博客项目 - 使用Redis缓存数据
  11. 基于 abp vNext 和 .NET Core 开发博客项目 - 集成Hangfire实现定时任务处理
  12. 基于 abp vNext 和 .NET Core 开发博客项目 - 用AutoMapper搞定对象映射
  13. 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(一)
  14. 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(二)
  15. 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(三)
  16. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(一)
  17. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(二)
  18. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(三)
  19. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(四)
  20. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(五)
  21. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(一)
  22. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(二)
  23. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(三)
  24. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(四)
  25. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(五)
  26. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(六)
  27. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(七)

上一篇完成了标签模块和友情链接模块的所有功能,本篇来继续完成博客最后的模块,文章的管理。

文章列表 & 删除

先将分页查询的列表给整出来,这块和首页的分页列表是类似的,就是多了个Id字段。

先添加两条路由规则。

  1. @page "/admin/posts"
  2. @page "/admin/posts/{page:int}"

新建返回数据默认QueryPostForAdminDto.cs

  1. //QueryPostForAdminDto.cs
  2. using System.Collections.Generic;
  3. namespace Meowv.Blog.BlazorApp.Response.Blog
  4. {
  5. public class QueryPostForAdminDto
  6. {
  7. /// <summary>
  8. /// 年份
  9. /// </summary>
  10. public int Year { get; set; }
  11. /// <summary>
  12. /// Posts
  13. /// </summary>
  14. public IEnumerable<PostBriefForAdminDto> Posts { get; set; }
  15. }
  16. }
  17. //PostBriefForAdminDto.cs
  18. namespace Meowv.Blog.BlazorApp.Response.Blog
  19. {
  20. public class PostBriefForAdminDto : PostBriefDto
  21. {
  22. /// <summary>
  23. /// 主键
  24. /// </summary>
  25. public int Id { get; set; }
  26. }
  27. }

然后添加所需的参数:当前页码、限制条数、总页码、文章列表返回数据模型。

  1. /// <summary>
  2. /// 当前页码
  3. /// </summary>
  4. [Parameter]
  5. public int? page { get; set; }
  6. /// <summary>
  7. /// 限制条数
  8. /// </summary>
  9. private int Limit = 15;
  10. /// <summary>
  11. /// 总页码
  12. /// </summary>
  13. private int TotalPage;
  14. /// <summary>
  15. /// 文章列表数据
  16. /// </summary>
  17. private ServiceResult<PagedList<QueryPostForAdminDto>> posts;

然后在初始化函数OnInitializedAsync()中调用API获取文章数据.

  1. /// <summary>
  2. /// 初始化
  3. /// </summary>
  4. protected override async Task OnInitializedAsync()
  5. {
  6. var token = await Common.GetStorageAsync("token");
  7. Http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
  8. // 设置默认值
  9. page = page.HasValue ? page : 1;
  10. await RenderPage(page);
  11. }
  12. /// <summary>
  13. /// 点击页码重新渲染数据
  14. /// </summary>
  15. /// <param name="page"></param>
  16. /// <returns></returns>
  17. private async Task RenderPage(int? page)
  18. {
  19. // 获取数据
  20. posts = await Http.GetFromJsonAsync<ServiceResult<PagedList<QueryPostForAdminDto>>>($"/blog/admin/posts?page={page}&limit={Limit}");
  21. // 计算总页码
  22. TotalPage = (int)Math.Ceiling((posts.Result.Total / (double)Limit));
  23. }

在初始化中判断page参数,如果没有值给他设置一个默认值1。RenderPage(int? page)方法是调用API返回数据,并计算出总页码值。

最后在页面上进行数据绑定。

  1. <AdminLayout>
  2. @if (posts == null)
  3. {
  4. <Loading />
  5. }
  6. else
  7. {
  8. <div class="post-wrap archive">
  9. <NavLink style="float:right" href="/admin/post"><h3>~~~ 新增文章 ~~~</h3></NavLink>
  10. @if (posts.Success && posts.Result.Item.Any())
  11. {
  12. @foreach (var item in posts.Result.Item)
  13. {
  14. <h3>@item.Year</h3>
  15. @foreach (var post in item.Posts)
  16. {
  17. <article class="archive-item">
  18. <NavLink title="删除" @onclick="@(async () => await DeleteAsync(post.Id))"></NavLink>
  19. <NavLink title="编辑" @onclick="@(async () => await Common.NavigateTo($"/admin/post/{post.Id}"))"></NavLink>
  20. <NavLink target="_blank" class="archive-item-link" href="@("/post" + post.Url)">@post.Title</NavLink>
  21. <span class="archive-item-date">@post.CreationTime</span>
  22. </article>
  23. }
  24. }
  25. <nav class="pagination">
  26. @for (int i = 1; i <= TotalPage; i++)
  27. {
  28. var _page = i;
  29. if (page == _page)
  30. {
  31. <span class="page-number current">@_page</span>
  32. }
  33. else
  34. {
  35. <a class="page-number" @onclick="@(() => RenderPage(_page))" href="/admin/posts/@_page">@_page</a>
  36. }
  37. }
  38. </nav>
  39. }
  40. else
  41. {
  42. <ErrorTip />
  43. }
  44. </div>
  45. }
  46. </AdminLayout>

HTML内容放在组件AdminLayout中,当 posts 没加载完数据的时候显示加载组件<Loading />

在页面上循环遍历文章数据和翻页页码,每篇文章标题前面添加两个按钮删除和编辑,同时单独加了一个新增文章的按钮。

删除文章调用DeleteAsync(int id)方法,需要传递参数,当前文章的id。

新增和编辑按钮都跳转到"/admin/post"页面,当编辑的时候将id也传过去即可,路由规则为:"/admin/post/{id}"。

删除文章``方法如下:

  1. /// <summary>
  2. /// 删除文章
  3. /// </summary>
  4. /// <param name="id"></param>
  5. /// <returns></returns>
  6. private async Task DeleteAsync(int id)
  7. {
  8. // 弹窗确认
  9. bool confirmed = await Common.InvokeAsync<bool>("confirm", "\n真的要干掉这篇该死的文章吗");
  10. if (confirmed)
  11. {
  12. var response = await Http.DeleteAsync($"/blog/post?id={id}");
  13. var result = await response.Content.ReadFromJsonAsync<ServiceResult>();
  14. if (result.Success)
  15. {
  16. await RenderPage(page);
  17. }
  18. }
  19. }

删除之前进行二次确认,避免误删,当确认删除之后调用删除文章API,最后重新渲染数据即可。

新增 & 更新文章

完成了后台文章列表的查询和删除,现在整个博客模块功能就差新增和更新文章了,胜利就在前方,冲啊。

这块的开发工作耗费了我太多时间,因为想使用 markdown 来写文章,找了一圈下来没有一个合适的组件,所以退而求次只能选择现有的markdown编辑器来实现了。

我这里选择了开源的编辑器Editor.md,有需要的可以去 Github 自己下载,https://github.com/pandao/editor.md

将下载的资源包解压放在 wwwroot 文件夹下,默认是比较大的,而且还有很多示例文件,我已经将其精简了一番,可以去我 Github 下载使用。

先来看下最终的成品效果吧。

是不是感觉还可以,废话不多说,接下里告诉大家如何实现。

在 Admin 文件夹下添加post.razor组件,设置路由,并且引用一个样式文件,在页面中引用样式文件好像不太符合标准,不过无所谓了,这个后台就自己用,而且还就这一个页面用得到。

  1. @page "/admin/post"
  2. @page "/admin/post/{id:int}"
  3. <link href="./editor.md/css/editormd.css" rel="stylesheet" />
  4. <AdminLayout>
  5. ...
  6. </AdminLayout>

把具体HTML内容放在组件AdminLayout中。

因为新增和编辑放在同一个页面上,所以当id参数不为空的时候需要添加一个id参数,同时默认一进来就让页面显示加载中的组件,当页面和数据加载完成后在显示具体的内容,所以在指定一个布尔类型的是否加载参数isLoading

我们的编辑器主要依赖JavaScript实现的,所以这里不可避免要使用到JavaScript了。

app.js中添加几个全局函数。

  1. switchEditorTheme: function () {
  2. editor.setTheme(localStorage.editorTheme || 'default');
  3. editor.setEditorTheme(localStorage.editorTheme === 'dark' ? 'pastel-on-dark' : 'default');
  4. editor.setPreviewTheme(localStorage.editorTheme || 'default');
  5. },
  6. renderEditor: async function () {
  7. await this._loadScript('./editor.md/lib/zepto.min.js').then(function () {
  8. func._loadScript('./editor.md/editormd.js').then(function () {
  9. editor = editormd("editor", {
  10. width: "100%",
  11. height: 700,
  12. path: './editor.md/lib/',
  13. codeFold: true,
  14. saveHTMLToTextarea: true,
  15. emoji: true,
  16. atLink: false,
  17. emailLink: false,
  18. theme: localStorage.editorTheme || 'default',
  19. editorTheme: localStorage.editorTheme === 'dark' ? 'pastel-on-dark' : 'default',
  20. previewTheme: localStorage.editorTheme || 'default',
  21. toolbarIcons: function () {
  22. return ["bold", "del", "italic", "quote", "ucwords", "uppercase", "lowercase", "h1", "h2", "h3", "h4", "h5", "h6", "list-ul", "list-ol", "hr", "link", "image", "code", "preformatted-text", "code-block", "table", "datetime", "html-entities", "emoji", "watch", "preview", "fullscreen", "clear", "||", "save"]
  23. },
  24. toolbarIconsClass: {
  25. save: "fa-check"
  26. },
  27. toolbarHandlers: {
  28. save: function () {
  29. func._shoowBox();
  30. }
  31. },
  32. onload: function () {
  33. this.addKeyMap({
  34. "Ctrl-S": function () {
  35. func._shoowBox();
  36. }
  37. });
  38. }
  39. });
  40. });
  41. });
  42. },
  43. _shoowBox: function () {
  44. DotNet.invokeMethodAsync('Meowv.Blog.BlazorApp', 'showbox');
  45. },
  46. _loadScript: async function (url) {
  47. let response = await fetch(url);
  48. var js = await response.text();
  49. eval(js);
  50. }

renderEditor主要实现了动态加载JavaScript代码,将markdown编辑器渲染出来。这里不多说,都是Editor.md示例里面的代码。

为了兼容暗黑色主题,这里还加了一个切换编辑器主题的JavaScript方法,switchEditorTheme

_shoowBox就厉害了,这个方法是调用的.NET组件中的方法,前面我们用过了在Blazor中调用JavaScript,这里演示了JavaScript中调用Blazor中的组件方法。

现在将所需的几个参数都添加到代码中。

  1. /// <summary>
  2. /// 定义一个委托方法,用于组件实例方法调用
  3. /// </summary>
  4. private static Func<Task> action;
  5. /// <summary>
  6. /// 默认隐藏Box
  7. /// </summary>
  8. private bool Open { get; set; } = false;
  9. /// <summary>
  10. /// 修改时的文章Id
  11. /// </summary>
  12. [Parameter]
  13. public int? Id { get; set; }
  14. /// <summary>
  15. /// 格式化的标签
  16. /// </summary>
  17. private string tags { get; set; }
  18. /// <summary>
  19. /// 默认显示加载中
  20. /// </summary>
  21. private bool isLoading = true;
  22. /// <summary>
  23. /// 文章新增或者修改输入参数
  24. /// </summary>
  25. private PostForAdminDto input;
  26. /// <summary>
  27. /// API返回的分类列表数据
  28. /// </summary>
  29. private ServiceResult<IEnumerable<QueryCategoryForAdminDto>> categories;

大家看看注释就知道参数是做什么的了。

现在我们在初始化函数中将所需的数据通过API获取到。

  1. /// <summary>
  2. /// 初始化
  3. /// </summary>
  4. /// <returns></returns>
  5. protected override async Task OnInitializedAsync()
  6. {
  7. action = ChangeOpenStatus;
  8. var token = await Common.GetStorageAsync("token");
  9. Http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
  10. if (Id.HasValue)
  11. {
  12. var post = await Http.GetFromJsonAsync<ServiceResult<PostForAdminDto>>($"/blog/admin/post?id={Id}");
  13. if (post.Success)
  14. {
  15. var _post = post.Result;
  16. input = new PostForAdminDto
  17. {
  18. Title = _post.Title,
  19. Author = _post.Author,
  20. Url = _post.Url,
  21. Html = _post.Html,
  22. Markdown = _post.Markdown,
  23. CategoryId = _post.CategoryId,
  24. Tags = _post.Tags,
  25. CreationTime = _post.CreationTime
  26. };
  27. tags = string.Join(",", input.Tags);
  28. }
  29. }
  30. else
  31. {
  32. input = new PostForAdminDto()
  33. {
  34. Author = "阿星Plus",
  35. CreationTime = DateTime.Now
  36. };
  37. }
  38. categories = await Http.GetFromJsonAsync<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>>("/blog/admin/categories");
  39. // 渲染编辑器
  40. await Common.InvokeAsync("window.func.renderEditor");
  41. // 关闭加载
  42. isLoading = !isLoading;
  43. }

action是一个异步的委托,在初始化中执行了ChangeOpenStatus方法,这个方法等会说,然后获取localStorage中token的值。

通过参数Id是否有值来判断当前是新增文章还是更新文章,如果有值就是更新文章,这时候需要根据id去将文章的数据拿到赋值给PostForAdminDto对象展示在页面上,如果没有可以添加几个默认值给PostForAdminDto对象。

因为文章需要分类和标签的数据,同时这里将分类的数据也查出来,标签默认是List列表,将其转换成字符串类型。

但完成上面操作后,调用JavaScript方法renderEditor渲染渲染编辑器,最后关闭加载,显示页面。

现在来看看页面。

  1. <AdminLayout>
  2. @if (isLoading)
  3. {
  4. <Loading />
  5. }
  6. else
  7. {
  8. <div class="post-box">
  9. <div class="post-box-item">
  10. <input type="text" placeholder="标题" autocomplete="off" @bind="@input.Title" @bind:event="oninput" @onclick="@(() => { Open = false; })" />
  11. <input type="text" placeholder="作者" autocomplete="off" @bind="@input.Author" @bind:event="oninput" @onclick="@(() => { Open = false; })" />
  12. </div>
  13. <div class="post-box-item">
  14. <input type="text" placeholder="URL" autocomplete="off" @bind="@input.Url" @bind:event="oninput" @onclick="@(() => { Open = false; })" />
  15. <input type="text" placeholder="时间" autocomplete="off" @bind="@input.CreationTime" @bind:format="yyyy-MM-dd HH:mm:sss" @bind:event="oninput" @onclick="@(() => { Open = false; })" />
  16. </div>
  17. <div id="editor">
  18. <textarea style="display:none;">@input.Markdown</textarea>
  19. </div>
  20. <Box OnClickCallback="@SubmitAsync" Open="@Open" ButtonText="发布">
  21. <div class="box-item">
  22. <b>分类:</b>
  23. @if (categories.Success && categories.Result.Any())
  24. {
  25. @foreach (var item in categories.Result)
  26. {
  27. <label><input type="radio" name="category" value="@item.Id" @onchange="@(() => { input.CategoryId = item.Id; })" checked="@(item.Id == input.CategoryId)" />@item.CategoryName</label>
  28. }
  29. }
  30. </div>
  31. <div class="box-item"></div>
  32. <div class="box-item">
  33. <b>标签:</b>
  34. <input type="text" @bind="@tags" @bind:event="oninput" />
  35. </div>
  36. </Box>
  37. </div>
  38. }
  39. </AdminLayout>

添加了四个input框,分别用来绑定标题、作者、URL、时间,<div id="editor"></div>中为编辑器所需。

然后我这里还是把之前的弹窗组件搞出来了,执行逻辑不介绍了,在弹窗组件中自定义显示分类和标签的内容,将获取到的分类和标签绑定到具体位置。

每个分类都是一个radio标签,并且对应一个点击事件,点哪个就把当前分类的Id赋值给PostForAdminDto对象。

所有的input框都使用@bind@bind:event绑定数据和获取数据。

Box弹窗组件这里自定义了按钮文字,ButtonText="发布"

  1. /// <summary>
  2. /// 改变Open状态,通知组件渲染
  3. /// </summary>
  4. private async Task ChangeOpenStatus()
  5. {
  6. Open = true;
  7. var markdown = await Common.InvokeAsync<string>("editor.getMarkdown");
  8. var html = await Common.InvokeAsync<string>("editor.getHTML");
  9. if (string.IsNullOrEmpty(input.Title) || string.IsNullOrEmpty(input.Url) ||
  10. string.IsNullOrEmpty(input.Author) || string.IsNullOrEmpty(markdown) ||
  11. string.IsNullOrEmpty(html))
  12. {
  13. await Alert();
  14. }
  15. input.Html = html;
  16. input.Markdown = markdown;
  17. StateHasChanged();
  18. }
  19. /// <summary>
  20. /// 暴漏给JS执行,弹窗确认框
  21. /// </summary>
  22. [JSInvokable("showbox")]
  23. public static void ShowBox()
  24. {
  25. action.Invoke();
  26. }
  1. /// <summary>
  2. /// alert提示
  3. /// </summary>
  4. /// <returns></returns>
  5. private async Task Alert()
  6. {
  7. Open = false;
  8. await Common.InvokeAsync("alert", "\n好像漏了点什么吧");
  9. return;
  10. }

现在可以来看看ChangeOpenStatus方法了,这个是改变当前弹窗状态的一个方法。为什么需要这个方法呢?

因为在Blazor中JavaScript想要调用组件内的方法,方法必须是静态的,那么只能通过这种方式去实现了,在静态方法是不能够直接改变弹窗的状态值的。

其实也可以不用这么麻烦,因为我在编辑器上自定义了一个按钮,为了好看一些所以只能曲折一点,嫌麻烦的可以直接在页面上搞个按钮执行保存数据逻辑也是一样的。

使用JSInvokableAttribute需要在_Imports.razor中添加命名空间@using Microsoft.JSInterop

ChangeOpenStatus中获取到文章内容:HTML和markdown,赋值给PostForAdminDto对象,要先进行判断页面上的几个参数是否有值,没值的话给出提示执行Alert()方法,最后使用StateHasChanged()通知组件其状态已更改。

Alert方法就是调用原生的JavaScriptalert方法,给出一个提示。

ShowBox就是暴漏给JavaScript的方法,使用DotNet.invokeMethodAsync('Meowv.Blog.BlazorApp', 'showbox');进行调用。

那么现在一切都正常进行的情况下,点击编辑器上自定义的保存按钮,页面上值不为空的情况下就会弹出我们的弹窗组件Box

最后在弹窗组件的回调方法中执行新增文章还是更新文章。

  1. /// <summary>
  2. /// 确认按钮点击事件
  3. /// </summary>
  4. /// <returns></returns>
  5. private async Task SubmitAsync()
  6. {
  7. if (string.IsNullOrEmpty(tags) || input.CategoryId == 0)
  8. {
  9. await Alert();
  10. }
  11. input.Tags = tags.Split(",");
  12. var responseMessage = new HttpResponseMessage();
  13. if (Id.HasValue)
  14. responseMessage = await Http.PutAsJsonAsync($"/blog/post?id={Id}", input);
  15. else
  16. responseMessage = await Http.PostAsJsonAsync("/blog/post", input);
  17. var result = await responseMessage.Content.ReadFromJsonAsync<ServiceResult>();
  18. if (result.Success)
  19. {
  20. await Common.NavigateTo("/admin/posts");
  21. }
  22. }

打开弹窗后执行回调事件之前还是要判断值是否为空,为空的情况下还是给出alert提示,此时将tags标签还是转换成List列表,根据Id是否有值去执行新增数据或者更新数据,最终成功后跳转到文章列表页。

本片到这里就结束了,主要攻克了在Blazor中使用Markdown编辑器实现新增和更新文章,这个系列差不多就快结束了,预计还有2篇的样子,感谢各位的支持。

开源地址:https://github.com/Meowv/Blog/tree/blog_tutorial

基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(八)的更多相关文章

  1. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(二)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  2. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(三)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  3. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(四)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  4. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(五)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  5. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(六)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  6. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(七)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  7. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(九)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  8. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(一)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  9. 基于 abp vNext 和 .NET Core 开发博客项目 - 终结篇之发布项目

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

随机推荐

  1. C#判断TCP连接状态

  2. 那些面试官必问的JAVA多线程和并发面试题及回答

    Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用.而线程是在进程中执行的一个任务.Java运行环 ...

  3. (板子) 最小生成树 买礼物 luogu P1194

    luogu题目传送门! 懒得找最小生成树模板了,就把这题当板子吧. 最小生成树,就是指对于一张图,我们将图转换成一棵树,连通的,同时让所有的边尽可能的小(废话). 最小生成树一般都采用Kruskal算 ...

  4. [Objective-C] 020_ Block

    1.定义和使用Block #import "ViewController.h" @interface ViewController () @end @implementation ...

  5. JSP指令 & 中文乱码问题

    1. JSP 指令: JSP指令(directive)是为JSP引擎而设计的,     它们并不直接产生任何可见输出, 而只是告诉引擎如何处理JSP页面中的其余部分. 2. 在JSP 2.0中,定义了 ...

  6. 曹工说mini-dubbo(2)--分析eureka client源码,想办法把我们的服务提供者注册到eureka server(上)

    前言 eureka是spring cloud Netflix技术体系中的重要组件,主要完成服务注册和发现的功能:那现在有个问题,我们自己写的rpc服务,如果为了保证足够的开放性和功能完善性,那肯定要支 ...

  7. xxshenqi分析报告

    背景 今年七夕爆发了一场大规模手机病毒传播,apk的名字叫做xxshenqi.中了这个病毒的用户会群发手机所有联系人一条信息,内容是包含这个apk下载的链接,同时用户的联系人信息和短信会被窃取,造成隐 ...

  8. SpringMVC(一)概述、解析器与注解

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.SpringMVC的概述 1.概述 Spring MVC框架是一个开源的Java平台,为开发强大的基 ...

  9. Java实现蓝桥杯 算法提高 八皇后 改

    **算法提高 8皇后·改** 时间限制:1.0s 内存限制:256.0MB 提交此题 问题描述 规则同8皇后问题,但是棋盘上每格都有一个数字,要求八皇后所在格子数字之和最大. 输入格式 一个8*8的棋 ...

  10. Java实现 LeetCode 438 找到字符串中所有字母异位词

    438. 找到字符串中所有字母异位词 给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引. 字符串只包含小写英文字母,并且字符串 s 和 p ...