Find the NuGet packages here:
Once installed add the following to your program.cs file:
builder.RegisterHotwiredDotNetCore();
If you want intellisense for the TurboStream ViewComponent, add the following to your _ViewImports.cshtml file:
@addTagHelper *, HotwiredDotNet.Ui
Turbo frames can be rendered using a tag-helper to generate the url like with the .net anchorTagHelper and formTagHelper.
<turbo-frame id="frame" asp-page="TurboFrame" asp-page-handler="OnGet" >
</turbo-frame>
This will result in:
<turbo-frame id="frame" src="https://localhost:44362/TurboFrame?handler=OnGet" complete="">
</turbo-frame>
It also works for mvc routes eg. asp-controller and asp-action
In a view you can use your view-component with either tag-helper syntax,
<vc:turbo-stream action="Update" target="stream-target" template="Html"></vc:turbo-stream>
or Component.Invoke
@await Component.InvokeAsync(TurboStream.Name, new
{
target = "stream-target",
action = TurboStreamAction.Append,
template = "Html content"
})
Sometimes it can be useful to get the Turbo-stream view component as a string, for example when you want to chain multiple Turbo-streams in one request
public async Task<IActionResult> OnGet()
{
//the HttpContext.SetTurboStreamMimeType(), is an extension method that sets the content-type header to text/vnd.turbo-stream.html, which allows turbo to pick up stream responses from normal http requests.
HttpContext.SetTurboStreamMimeType();
var resultString = await TurboStream.AsStringAsync(_razorViewComponentStringRenderer, "first-target", TurboStreamAction.Append, "Html");
resultString += await TurboStream.AsStringAsync(_razorViewComponentStringRenderer, "second-target", TurboStreamAction.Append, "Html");
return new ContentResult(resultString);
}
The RazorViewComponentStringRenderer can be used to make a view component render as a string.
public async Task<IActionResult> OnGetRenderComponent()
{
HttpContext.SetTurboStreamMimeType();
var template = await _razorViewComponentStringRenderer.RenderAsync(AlertViewComponent.Name, new
{
alertType = AlertType.Success,
text = "I'm a view component rendered from a turbo stream"
});
return ViewComponent(TurboStream.Name,new { target = "http-stream", action = TurboStreamAction.Append, template = template });
}
Alternatively there is another helper method on TurboStream that can be used to render a view component inside a stream response
public async Task<IActionResult> OnGetRenderComponent()
{
HttpContext.SetTurboStreamMimeType();
return ContentResult(await TurboStream.AsStringAsync(_razorViewComponentStringRenderer, "target", TurboStreamAction.Append,
AlertViewComponent.Name, new
{
alertType = AlertType.Success,
text = "I'm a view component rendered from a turbo stream"
});
}
Turbo expects the server to return 303 on form submission. To better allow for this, a new ActionResult has been introduces called SeeOther. While a standard 302 redirect will sometimes work it's important to note the differences between 302 and 303. 302 will redirect but if you use put, delete or patch the same method will be used for the redirect while 303 will always use GET after a redirect:.
fetch original method | fetch original response | redirect method |
---|---|---|
GET | 302 | GET |
GET | 303 | GET |
POST | 302 | GET |
POST | 303 | GET |
PUT | 302 | PUT |
PUT | 303 | GET |
PATCH | 302 | PATCH |
PATCH | 303 | GET |
DELETE | 302 | DELETE |
DELETE | 303 | GET |
public IActionResult OnPost()
{
//You can pass data between pages using the TempData dictionary as normal
AlertHelper.SetAlert(TempData, "I'm an alert from before the redirect");
AlertHelper.SetAlertType(TempData, AlertType.Warning);
//The see other result allows ud to use the Post/Redirect/Get pattern https://en.wikipedia.org/wiki/Post/Redirect/Get
//This is necessary since turbo expects form redirects to be 303 and not 301 or 302
return new SeeOtherResult("SeeOtherPage");
}
Sometimes it makes sense to return the current page after a form submission, for this there is an extension method on Page and View results.
public IActionResult OnPost()
{
return Page().SeeOther();
}
public IActionResult OnPost()
{
return View().SeeOther();
}
for form errors you can use a similar extension method on the Page and View results to change the status code to 422, since turbo expects either 422 or 50X on form errors.
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page().UnprocessableEntity();
}
return Page().SeeOther();
}
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return View().UnprocessableEntity();
}
return View().SeeOther();
}
This is all the stuff that works behind the scenes, that you don't have to actively use.
A tag-helper that checks whether an anchor tag has the data-turbo-method attribute. If it does, it adds an anti-forgery token so that when turbo generates a form it validate correctly.
The TurboFrame middleware removes unused html from the response body when the request has the turbo-frame header present, which is default in turbo-frame requests. This is done to reduce the amount of data sent over the wire. It also ensures that any turbo-streams will not be removed from the response. It's preferable to handle this in your own code, but it can be quite cumbersome to add this logic in to an existing application. Especially when trying to handle both frames and streams.
To add the middleware to your pipeline, add the following to your Startup.cs/Program.cs file:
app.UseTurboFrameMiddleware();
NOTE: The Turbo community as opposed to the htmx community prefers sending fully formed HTML pages. This pattern is a deviation from that.
The socket implementation seen in the demo project is a work in progress. I'm still trying to figure out if there is a good way to make some generic implementation.