lin-cms-dotnetcore: an ASP.NET Core CMS backend with a five-project layout
😃A simple and practical CMS implemented by .NET + FreeSql;前后端分离、Docker部署、OAtuh2授权登录、自动化部署DevOps、自动同步至Gitee、代码生成器、仿掘金专栏
At a glance
- What is it?
- Lin CMS by .NET is a backend implementation of the Lin CMS framework, written in C# on ASP.NET Core with FreeSql, Serilog, AutoMapper and JWT authentication, split into five layered projects and shipped with Docker and CI scripts. Its sharpest edge is the JWT signing key, which the application now refuses to start without in production.
- Who is it for?
- Adopt lin-cms-dotnetcore if you want a working CMS skeleton in C# rather than a framework to design from, and if you already have Vue skills or a separate frontend team to handle the separated admin and user interfaces. Do not adopt it for a public content site that depends on search engine indexing, since the project is built around separated frontends with server rendering deliberately left out of scope.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 23 days ago.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A backend for the Lin CMS family, not a CMS on its own
Lin CMS is a content management framework that grew out of a Chinese team's project work, and this repository is the ASP.NET Core implementation of it. The author describes it as a personal project created after Lin CMS had already been implemented in Python and Node.js, and a separate PHP implementation exists too. That places lin-cms-dotnetcore as one language binding in a family rather than as the original.
What the repository delivers is the server. The user interface is deliberately absent from this project: the admin console lives in a separate repository named lin-cms-vue, and the end-user application lives in another named lin-cms-vvlog. The README's own explanation of that split is worth repeating, because it is the central design decision. A traditional site renders pages on the server with a template language such as Razor, which helps with search engine visibility but is rarely relevant for an internal management system, and it leaves the page markup to whichever side of the team is less suited to it. Separating the two lets the frontend be written in Vue while the server exposes only APIs.
The feature list follows from that: user management, permissions and a logging system are built in, and on top of them sit dictionary item management, an essay or column section modelled on a technical blogging platform, comments and likes, notifications, tags and channels grouped by tag. The backend mirrors are published on both Gitee and GitHub, and the API documentation is served through Swagger at a public demo address.
The five projects inside src
The source tree is organized as a classic layered architecture, and the README names each layer and its responsibility. LinCms.Web is the API layer, an ASP.NET Core host that exposes the HTTP endpoints. LinCms.Application holds the application services, which is where the use cases live. LinCms.Application.Contracts holds the data transfer objects and the interfaces those application services expose, so that a caller can depend on an abstraction without referencing the implementation.
Below that, LinCms.Infrastructure carries the persistence side of the system, the database access, and LinCms.Core holds the domain itself: entity classes, shared operation classes, AOP extensions, pagination objects, base dependency interfaces, time extension methods, the current user information, exception types and value objects. A separate test directory sits alongside src.
The dependency direction follows the layering, and the value for a reader trying the project out is that each layer has one job. A change to the storage engine touches Infrastructure, a new business rule touches Application and Contracts, and an HTTP contract change touches Web. The stated design practices match this structure: repository pattern, separation of concerns, SOLID, clean code, domain-driven design and automated database migrations.
The project is split with Directory.Build.props and a solution file, LinCms.sln, and the repository keeps a .editorconfig and a .gitattributes, which are the usual markers of a team that expects consistent formatting and line endings across a large generated codebase.
The data, mapping and logging stack
Persistence runs through FreeSql, an ORM, against a list of supported engines: MySQL 5.6, Microsoft SQL Server, Oracle, SQLite and PostgreSQL. FreeSql is also the layer that carries the automated database migrations the project lists among its practices. Choosing the engine is a deployment decision rather than a code one, which is one of the reasons this skeleton suits teams that already have a database platform.
On top of the ORM sit AutoMapper for object mapping and Newtonsoft.Json for serialization. Dependency injection comes from Autofac through its ASP.NET Core integration, and the project also references a general-purpose extension method package called Z.ExtensionMethods. Logging is Serilog, with the aspnetcore integration.
The API surface is documented three ways, which tells you something about the intended audience. Swagger is provided through Swashbuckle.AspNetCore, but the project also ships RapiDoc and a Knife4j UI, both through the author's own IGeekFan.AspNetCore forks. Knife4j is the Chinese-friendly interface for the same OpenAPI document. Tests use Xunit.
Beyond the application core, two integrations deserve a mention. Distributed transactions and the event bus come from DotNeteCore.CAP, and cloud storage goes through MQiniu.Core, a wrapper for the Qiniu object storage service. Both are optional in the sense that they address needs a specific deployment has, but they are dependencies declared in the project, so they are part of the shape you inherit.
The JWT signing key is now a startup requirement
The most consequential behaviour in the project is how it handles the JWT signing key, and the README is unusually detailed about it. In production the key is configured as Authentication:JwtBearer:SecurityKey in appsettings.Production.json, or supplied through an environment variable or secret manager using the double underscore form Authentication__JwtBearer__SecurityKey. Each deployment should use its own cryptographically random key of at least 64 characters, and it must never be committed to Git.
The generation snippet in the README is a PowerShell one, and it is the shortest path to a compliant value:
$jwtKeyBytes = New-Object byte[] 64
[System.Security.Cryptography.RandomNumberGenerator]::Fill($jwtKeyBytes)
$env:Authentication__JwtBearer__SecurityKey = [Convert]::ToBase64String($jwtKeyBytes)In development and test environments, a missing configuration is not fatal. The application generates a random key and writes it back to appsettings.json in the run directory, reusing that value on later starts. In production the same write-back goes to appsettings.Production.json. What is fatal is a key that is too short or one that still uses the repository's old default value, and in those cases the application refuses to start rather than falling back.
The validation side is stricter than a signature check. Verification also reads the user's current status and loads current roles and groups from the database, so a disabled or deleted account cannot keep using an old token. A deployment that has been running with the published default key must rotate it and restart every instance, which invalidates existing access tokens and forces users to log in again.
Deployment through run.sh and the CI pipelines
The repository root holds the operational scripts. run.sh is the container entry path, and the README explains its handling of secrets: when JWT_SIGNING_KEY is provided from a protected deployment environment, the script passes it into the container. When that variable is omitted, the application falls back to the mounted appsettings.Production.json, and if configuration is still missing it writes a local key file. The advice is to persist the generated value in appsettings.Production.json or in a secret manager and to keep every instance of one deployment on the same key, rather than regenerating on each restart.
There is also a .dockerignore at the root, and build_push.sh for building and pushing the image. build-all.ps1, publish.bat and a batch file for deleting bin and obj folders round out the Windows-side tooling, which makes sense given that the repository was developed with Rider on Windows.
Continuous integration is configured twice over. azure-pipelines.yml drives the Azure DevOps path, and the .github directory holds the GitHub Actions workflow, whose Publish badge appears at the top of the README. The project description also mentions automatic synchronisation to Gitee, matching the Gitee mirror of the backend. A .vscode directory at the root suggests the author develops against Visual Studio Code as well as Rider.
The consequence for an adopter is that the deployment story is assembled rather than packaged. There is no single documented install-and-run command in the README, and the pieces have to be composed: a database, a secret for the signing key, a container image and a pipeline definition.
Where this CMS shape is the wrong choice
The framework's own documentation supplies the strongest argument against it. Because Lin CMS separates the frontend and backend and treats server-side rendering as out of scope for an internal tool, a project that depends on search engine indexing is served badly by the default arrangement. For an internal admin console that is the right trade. For a public blog or documentation site it is the wrong one, and a conventional server-rendered stack, where the same Razor templates produce pages and the API exists only where needed, fits better without any change to how the team works.
There is a second cost: the frontend is a separate, non-trivial application. The README says outright that you need to be familiar with C# and Vue, and lists ES6, axios, Element UI, webpack, Vuex and Vue Router on the frontend side. A backend team with no frontend capability inherits two codebases and a build pipeline for each.
Third, versioning is thin. The repository publishes no GitHub releases, so there is no tagged version to pin, and the changelog lives in the project wiki instead. For a skeleton that teams fork and then own, that matters less than for a library, but it does mean upgrades arrive as diffs against master rather than as version bumps, and a fork has no defined upstream to merge from.
Third-party sign-in exists for GitHub, QQ and Gitee through the AspNet.Security.OAuth provider packages, and the project description also mentions OAuth2 login and a code generator, so the missing pieces are not as numerous as the layered structure might suggest.
Licence, activity and how the project documents itself
The project is released under the MIT licence, with a LICENSE file at the repository root. MIT is permissive and places no conditions on commercial use or on redistribution of derived work, which suits a skeleton that most teams will fork and modify. This is a description of the licence terms rather than legal advice, and an organisation with its own review process should read the file in the repository.
On activity, the last push to the repository was on 2026-09-30, and the badges show a .NET 8.0.0 target and MIT licensing. The project publishes documentation at igeekfan.cn/dotnetcore/lin-cms/, marked in the README as still being improved, alongside a separate Lin CMS documentation site for the framework as a whole.
The audience is visible too. There is a QQ group for the .NET Core community around the project and a WeChat public account, and the author identifies as IGeekFan, who also maintains the RapiDoc and Knife4j UI packages the project depends on. That concentration of authorship is worth noting for anyone planning to rely on the framework long term: the person behind the CMS is the person behind several of its dependencies, so continuity of maintenance and continuity of the surrounding tooling are tied together.
Editorial conclusion
Adopt lin-cms-dotnetcore if you want a working CMS skeleton in C# rather than a framework to design from, and if you already have Vue skills or a separate frontend team to handle the separated admin and user interfaces. Do not adopt it for a public content site that depends on search engine indexing, since the project is built around separated frontends with server rendering deliberately left out of scope. Before you deploy, set Authentication__JwtBearer__SecurityKey to a random value of at least 64 characters through your secret store, rotate any deployment still using the repository's published default, and confirm that your chosen database from FreeSql's supported list is provisioned before the first migration.
Frequently asked questions
How do I configure the JWT signing key for lin-cms-dotnetcore?
Set Authentication__JwtBearer__SecurityKey as an environment variable, or Authentication:JwtBearer:SecurityKey in appsettings.Production.json, using a random value of at least 64 characters. In development the app generates and writes back a key to appsettings.json, but in production a short key or the old repository default makes it refuse to start.
Which databases does lin-cms-dotnetcore support?
Persistence goes through the FreeSql ORM, and the README lists MySQL 5.6, Microsoft SQL Server, Oracle, SQLite and PostgreSQL as the supported engines.
Does lin-cms-dotnetcore include a frontend?
No. The backend is separated from the interfaces: the admin console lives in the lin-cms-vue repository and the end-user application in lin-cms-vvlog, and the README says you need to be comfortable with both C# and Vue.
How is lin-cms-dotnetcore deployed and licensed?
The project ships run.sh for container deployment, build_push.sh for images, azure-pipelines.yml for Azure DevOps and a GitHub Actions workflow, with automatic synchronisation to Gitee. It is released under the MIT licence and publishes no GitHub releases, keeping its changelog in the project wiki.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/luoyunchong-lin-cms-dotnetcore)Was this useful?