This Blog App demonstrates some of the capabilities in ServiceStack Sharp Apps - an exciting real-time development model for developing .NET Core Apps where entire Sharp Apps can be developed within a live hot-reload experience without any compilation, build tools, dependencies, IDEs or any C# source code necessary by using the powerful and user-friendly #Script language and its comprehensive built-in functionality.
Ultimate Simplicity
This eliminates much of the complexity inherent in developing .NET Web Applications which by their nature results in highly customizable Web Apps where their entire functionality can be modified in real-time whilst the App is running, which is simple enough to be enhanced by non-developers like Designers and Content Creators courtesy of its approachable Handlebars-like and familiar JavaScript syntax.
Compiled Apps can have a prohibitively large barrier to entry where any modification often requires downloading source code separately, setting up a matching development environment with appropriate extensions and correct versions and non cursory level of experience with their chosen language, frameworks, build tools and other platform idiosyncrasies.
By contrast Sharp Apps require no development environment, no IDE's or build tools with all source code already included as part of the App which can be modified in real-time by any text editor to instantly view changes as they're made. So Apps like http://redis.web-app.io which provide a rich Admin UI for searching, browsing and modifying Redis's core data structures, can be easily enhanced by modifying a single index.html at the same time as using the App.
Blog App Features
This /Blog App is another example of encapsulating useful functionality in a highly customizable .NET Core Web App which to maximize approachability has no C# source code, plugins and uses no JavaScript or CSS frameworks. The development of which ended up being one of the most enjoyable experiences we've had in recent memory where all the usual complexities of developing a C# Server and modern JS App has been dispensed and you can just focus on the App you want to create, using a plain-text editor on the left, a live updating browser on the right and nothing else to interrupt your flow.
Any infrastructure dependencies have also been avoided by using SQLite by default which is automatically created an populated on first run if no database exists, or if preferred can be changed to use any other popular RDBMS using just config.
Multi User Blogging Platform
Any number of users can Sign In via Twitter and publish content under their Twitter Username where only they'll be able to modify their own Content. Setting up Twitter is as easy as it can be which just requires modifying the Twitter Auth Config in app.settings with the URL where the blog is hosted and the OAuth Keys for the Twitter OAuth App created at https://apps.twitter.com
Rich Content
Whilst most blogging platforms just edit static text, each Post content has access to the powerful and
Sandboxed features in https://sharpscript.net which can be used to create
Live Documents or Render Markdown which is itself just
one of the available blocks where it will render to HTML any content between the markdown
blocks:
{{#markdown}}
## Markdown Content
{{/markdown}}
By default the Markdig implementation is used to render Markdown but can also be configured to use an alternate Markdown provider.
Rich Markdown Editor
To make it easy to recall Markdown features, each Content is equipped with a Rich Text editor containing the most popular formatting controls along with common short-cuts for each feature, discoverable by hovering over each button:
The Editor also adopts popular behavior in IDEs where Tab
and SHIFT+Tab
can be used to indent blocks of text and lines can be commented with
Ctrl+/
or blocks with CTRL+SHIFT+/
.
Another nice productivity win is being able to CTRL+CLICK
on any Content you created to navigate to its Edit page.
Auto saved drafts
The content in each Text input
and textarea
is saved to localStorage
on each key-press so you can freely reload pages and navigate
around the site where all unpublished content will be preserved upon return.
When you want to revert back to the original published content you can clear the text boxes and reload the page which will load content from
the database instead of localStorage
.
Server Validation
The new.html and edit.html pages shows examples of performing server validation with #Script:
{{ assignErrorAndContinueExecuting: ex }}
{{ 'Title must be between 5 and 200 characters'
| onlyIf(length(postTitle) < 5 || length(postTitle) > 200) | assignTo: titleError }}
{{ 'Content must be between 25 and 64000 characters'
| onlyIf(length(content) < 25 || length(content) > 64000) | assignTo: contentError }}
{{ 'Potentially malicious characters detected'
| ifNotExists(contentError) | onlyIf(containsXss(content)) | assignTo: contentError }}
For more info see the docs on Error Handling.
Live Previews
Creating and Posting content benefit from Live Previews where its rendered output can be visualized in real-time before it's published.
Any textarea can easily be enhanced to enable Live Preview by including the data-livepreview
attribute with the element where the output
should be rendered in, e.g:
<textarea data-livepreview=".preview"></textarea>
<div class="preview"></div>
The implementation of which is surprisingly simple where the JavaScript snippet in default.js below is used to send their content on each change:
// Enable Live Preview of new Content
textAreas = document.querySelectorAll("textarea[data-livepreview]");
for (let i = 0; i < textAreas.length; i++) {
textAreas[i].addEventListener("input", livepreview, false);
livepreview({ target: textAreas[i] });
}
function livepreview(e) {
let el = e.target;
let sel = el.getAttribute("data-livepreview");
if (el.value.trim() == "") {
document.querySelector(sel).innerHTML = "<div>Live Preview</div>";
return;
}
let formData = new FormData();
formData.append("content", el.value);
fetch("/preview", {
method: "post",
body: formData
}).then(function(r) { return r.text(); })
.then(function(r) { document.querySelector(sel).innerHTML = r; });
}
To the /preview.html API Page which just renders and captures any #Script Content its sent and returns the output:
{{ content | evalScript({use:{plugins:'MarkdownScriptPlugin'}}) | assignTo:response }}
{{ response | return({ contentType:'text/plain' }) }}
By default the evalScript
method renders #Script in a new ScriptContext
which can be customized to utilize any additional
plugins
, script methods
and blocks
available in the configured SharpPagesFeature,
or for full access you can use {use:{context:true}}
to evaluate any #Script content under the same context that evalScript
is run on.