I've got to the bottom of this now. After all the trial and error described in my question the solution turns out to involve surprisingly little code.
Example With Server-Side Blazor And MVC In The Different Projects In The Same Solution
The answer below assumes you have a working server-side Blazor project in your solution and are trying to use components from that project in a separate MVC project in that same solution. I did this in VS2019, using Core 3 Preview 5.
See the bottom of the page or a link to an example of MVC and Blazor in the same project.
Changes to the MVC project's .csproj file
You just need to add a reference to your blazor project:
<ProjectReference Include="..MyClient.Web.BlazorMyClient.Web.Blazor.csproj" />
Changes to the MVC project's ViewsShared\_Layout.cshtml file
Add the base url into the head:
<head>
<meta charset="utf-8" />
<base href="~/" />
Add a reference to the Blazor JS Script
<script src="_framework/blazor.server.js"></script>
(This doesn't actually exist on your local file system, Blazor automagically sorts it out by the time the app gets to the browser)
Changes to the MVC project's Startup.cs
This is where the real work is done
In the ConfigureServices
method add:
services.AddServerSideBlazor();
I then switched my configuration over to the new style Core 3 configuration (so no longer using the AddMvc
method):
services.AddControllersWithViews()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
});
services.AddRazorPages();
And then the change that finally got it all working for me was to switch to Core 3's endpoint routing in the Configure(IApplicationBuilder app, IWebHostEnvironment env)
method:
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
endpoints.MapBlazorHub();
});
//app.UseMvc(routes =>
//{
// routes.MapRoute(
// name: "MyArea",
// template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
// routes.MapRoute(
// name: "default",
// template: "{controller=Home}/{action=Index}/{id?}");
//});
The commented out code shows the old style routing I was using. I think it was the absence of endpoints.MapBlazorHub();
that was causing my button click issue
Changes to the Blazor project
No changes were needed to the Blazor project to get this working.
How to then put the Blazor component on an MVC page
Once you've done all of the above, all that's needed to get your component to render on an MVC page is to add
@(await Html.RenderComponentAsync<YourComponent>())
That should work on both an MVC page and a Razor page.
Known routing issue in Core 3 Preview 5
The above changes were all that I needed to make to get it working. There is a known issue with Core 5 where once you navigate to an MVC or Razor page with a Blazor component on it, the routing will no longer work until the page is refreshed - the URLs will change in the browser's address bar, but no navigation will occur.
I'm now running on Preview 6 and I can confirm the routing issue is now fixed from Preview 6 onward.
Example With Server-Side Blazor And MVC In The Same Project
Chris Sainty has an example of server-side Blazor components added in to an existing MVC project here: Using Blazor Components In An Existing MVC Application (source here)