Combining Blazor WebAssembly and .Net MVC application - part 2
Tags: blazor, webassembly, dotnet, csharp
Published: 2023-08-14 08:17:57

In this article we will continue our journey of combining Blazor WebAssembly and .NET MVC application in a single project. This is part 2 of a series of articles that explains how to use Blazor components in MVC views and vice versa. If you haven’t read [part 1](https://dev.to/anpx00/combining-blazor-webassembly-and-net-mvc-application-part-1-pn9) yet, I recommend you to do so before continuing.

In [part 1](https://dev.to/anpx00/combining-blazor-webassembly-and-net-mvc-application-part-1-pn9), we have seen how to create a new Blazor WebAssembly project and add it as a reference to our existing MVC project. We have also seen how to configure our MVC project to serve the Blazor files. 

In this part, we will use Blazor WebAssembly componet in .NET MVC application to create a simple CRUD data application. Continue use the project in part 1, and the existed **FetchData** component in Blazor WebAssembly project.

### Add the component to MVC application to show the list
First, create the `Models` folder in Blazor WebAssemply project then move the `WeatherForecast` class in **FetchData** component to the folder. Add the `Id` field in `WeatherForecast` class:
```csharp
namespace BlazorApp.Models;

public class WeatherForecast
{
    public string? Id { get; set; }

    public DateOnly Date { get; set; }

    public int TemperatureC { get; set; }

    public string? Summary { get; set; }

    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
```
Then, in the `HomeController.cs` in MVC project, add some code to simulation data service:
```csharp
    public class HomeController : Controller
    {
        //simulation data service
        private static readonly List<WeatherForecast> weatherForecasts = new()
        {
            new WeatherForecast { Id = Guid.NewGuid().ToString(), Date = DateOnly.Parse("2022-01-06"), TemperatureC = 1, Summary = "Freezing", },
            new WeatherForecast { Id = Guid.NewGuid().ToString(), Date = DateOnly.Parse("2022-01-07"), TemperatureC = 14, Summary = "Bracing", },
            new WeatherForecast { Id = Guid.NewGuid().ToString(), Date = DateOnly.Parse("2022-01-08"), TemperatureC = -13, Summary = "Freezing", },
            new WeatherForecast { Id = Guid.NewGuid().ToString(), Date = DateOnly.Parse("2022-01-09"), TemperatureC = -16, Summary = "Balmy", },
            new WeatherForecast { Id = Guid.NewGuid().ToString(), Date = DateOnly.Parse("2022-01-10"), TemperatureC = -2, Summary = "Chilly", },
        };

        //the api to return data
        public IActionResult WeatherForecasts()
        {
            return Json(weatherForecasts);
        }
```
In the view `Index.cshtml`, replace code to like this:
```html
<component type="typeof(BlazorApp.Pages.FetchData)" render-mode="WebAssemblyPrerendered"></component>

@section Scripts{
    <script src="_framework/blazor.webassembly.js"></script>
}
```
Add the **HttpClient** to the .NET MVC application by this code in `Program.cs` file:
```csharp
//add httpclient
builder.Services.AddScoped(sp => new HttpClient());
```
This is because the **FetchData** component use HttpClient

```csharp
@inject HttpClient Http
```

so we need to add the **HttpClient** to the .NET MVC project, because when the .NET MVC application pre-render **FetchData** component, it must create a **HttpClient** instance and inject to component.

Last, in the **FetchData** component in Blazor WebAssembly, replace the `OnInitializedAsync` method with this:
```csharp
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("Home/WeatherForecasts");
            //force view update
            StateHasChanged();
        }
    }
```
Run the .NET MVC project and see the result:

![Blazor WebAssemply and .NET MVC](https://cdn.freemailforward.net/assets/images/uploaded/blog/create-blazor-app-4.webp)

### Add the Create function
In .NET MVC project, add create action in `HomeController` as a create api:
```csharp
    [HttpPost]
    public IActionResult WeatherForecasts([FromBody] WeatherForecast model)
    {
        if (ModelState.IsValid)
        {
            model.Id = Guid.NewGuid().ToString();
            weatherForecasts.Add(model);
        }
        return Json(weatherForecasts);
    }
```
Change the **FetchData** component like this to add the Create method on view:
```html
@using BlazorApp.Models;
@inject HttpClient Http

<PageTitle>Weather forecast</PageTitle>

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <EditForm Model="@createModel" OnValidSubmit="OnCreateFormSubmit" id="form-create">
        <DataAnnotationsValidator />
        <ValidationSummary />
    </EditForm>

    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
                <th>Action</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>
                    <InputDate class="form-control" @bind-Value="@createModel.Date" form="form-create"></InputDate>
                </td>
                <td>
                    <InputNumber class="form-control" @bind-Value="@createModel.TemperatureC" form="form-create"></InputNumber>
                </td>
                <td>
                    @createModel.TemperatureF
                </td>
                <td>
                    <InputText class="form-control" @bind-Value="@createModel.Summary" form="form-create"></InputText>
                </td>
                <td>
                    <button type="submit" class="btn btn-primary" form="form-create">Add</button>
                </td>
            </tr>

            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                    <td></td>
                </tr>
            }
        </tbody>
    </table>

}

@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("Home/WeatherForecasts");
            //force view update
            StateHasChanged();
        }
    }

    WeatherForecast createModel = new();

    async Task OnCreateFormSubmit()
    {
        using var createReq = await Http.PostAsJsonAsync("Home/WeatherForecasts", createModel);
        createReq.EnsureSuccessStatusCode();
        forecasts = await createReq.Content.ReadFromJsonAsync<WeatherForecast[]>();
    }
}
```
Save file then run the .NET MVC applicaton:

![Blazor WebAssembly Create function](https://cdn.freemailforward.net/assets/images/uploaded/blog/create-blazor-app-5.webp)

### The Edit function
Add the Edit function is same as the Create function, first, add the api action in `HomeController`:
```csharp
    [HttpPut]
    public IActionResult WeatherForecasts(string id, [FromBody] WeatherForecast model)
    {
        if (ModelState.IsValid)
        {
            var weatherForecast = weatherForecasts.FirstOrDefault(x => x.Id == id);
            if (weatherForecast != null)
            {
                weatherForecast.Date = model.Date;
                weatherForecast.TemperatureC = model.TemperatureC;
                weatherForecast.Summary = model.Summary;
            }
        }
        return Json(weatherForecasts);
    }
```
In the `WeatherForecast` class, add the `IsEditting` field to mark the editting entity:
```csharp
public bool IsEditting { get; set; }
```
Then edit the **FetchData** component like this:
```html
@using BlazorApp.Models;
@inject HttpClient Http

<PageTitle>Weather forecast</PageTitle>

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <EditForm Model="@createModel" OnValidSubmit="OnCreateFormSubmit" id="form-create">
        <DataAnnotationsValidator />
        <ValidationSummary />
    </EditForm>

    <EditForm Model="@editModel" OnValidSubmit="OnEditFormSubmit" id="form-edit">
        <DataAnnotationsValidator />
        <ValidationSummary />
    </EditForm>

    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
                <th>Action</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>
                    <InputDate class="form-control" @bind-Value="@createModel.Date" form="form-create"></InputDate>
                </td>
                <td>
                    <InputNumber class="form-control" @bind-Value="@createModel.TemperatureC" form="form-create"></InputNumber>
                </td>
                <td>
                    @createModel.TemperatureF
                </td>
                <td>
                    <InputText class="form-control" @bind-Value="@createModel.Summary" form="form-create"></InputText>
                </td>
                <td>
                    <button type="submit" class="btn btn-primary" form="form-create">Add</button>
                </td>
            </tr>

            @foreach (var forecast in forecasts)
            {
                if (forecast.IsEditting)
                {
                    <tr>
                        <td>
                            <InputDate class="form-control" @bind-Value="@editModel.Date" form="form-edit"></InputDate>
                        </td>
                        <td>
                            <InputNumber class="form-control" @bind-Value="@editModel.TemperatureC" form="form-edit"></InputNumber>
                        </td>
                        <td>
                            @editModel.TemperatureF
                        </td>
                        <td>
                            <InputText class="form-control" @bind-Value="@editModel.Summary" form="form-edit"></InputText>
                        </td>
                        <td>
                            <button type="submit" class="btn btn-outline-primary" form="form-edit">Save</button>
                            <button type="button" class="btn btn-outline-secondary" @onclick="() => { forecast.IsEditting = false; }">Cancel</button>
                        </td>
                    </tr>
                }
                else
                {
                    <tr>
                        <td>@forecast.Date.ToShortDateString()</td>
                        <td>@forecast.TemperatureC</td>
                        <td>@forecast.TemperatureF</td>
                        <td>@forecast.Summary</td>
                        @if (forecasts.All(s => !s.IsEditting))
                        {
                            <td>
                                <button type="button" class="btn btn-outline-primary" @onclick="() => { (editModel = forecast).IsEditting = true; }">Edit</button>
                            </td>
                        }
                        else
                        {
                            <td></td>
                        }
                    </tr>
                }
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("Home/WeatherForecasts");
            //force view update
            StateHasChanged();
        }
    }

    WeatherForecast createModel = new();

    async Task OnCreateFormSubmit()
    {
        using var createReq = await Http.PostAsJsonAsync("Home/WeatherForecasts", createModel);
        createReq.EnsureSuccessStatusCode();
        forecasts = await createReq.Content.ReadFromJsonAsync<WeatherForecast[]>();
    }

    WeatherForecast editModel = new();

    async Task OnEditFormSubmit()
    {
        using var createReq = await Http.PutAsJsonAsync($"Home/WeatherForecasts/{editModel.Id}", editModel);
        createReq.EnsureSuccessStatusCode();
        forecasts = await createReq.Content.ReadFromJsonAsync<WeatherForecast[]>();
    }
}
```
Now, run the .NET MVC application:

![Blazor WebAssemply edit funtion](https://cdn.freemailforward.net/assets/images/uploaded/blog/create-blazor-app-6.webp)

### The Delete function
In the `HomeController`, add this method:
```csharp
    [HttpDelete]
    public IActionResult WeatherForecasts(string id)
    {
        var weatherForecast = weatherForecasts.FirstOrDefault(x => x.Id == id);
        if (weatherForecast != null)
        {
            weatherForecasts.Remove(weatherForecast);
        }
        return Json(weatherForecasts);
    }
```
In **FetchData** component, add delete method:
```csharp
    async Task OnDeleteSubmit(string id)
    {
        using var createReq = await Http.DeleteAsync($"Home/WeatherForecasts/{id}");
        createReq.EnsureSuccessStatusCode();
        forecasts = await createReq.Content.ReadFromJsonAsync<WeatherForecast[]>();
    }
```
Then add Delete button after Edit button:
```html
    <td>
        <button type="button" class="btn btn-outline-primary" @onclick="() => { (editModel = forecast).IsEditting = true; }">Edit</button>
        <button type="button" class="btn btn-outline-danger" @onclick="async () => await OnDeleteSubmit(forecast.Id!)">Delete</button>
    </td>
```
Run the application and done:

![Blazor WebAssembly delete function](https://cdn.freemailforward.net/assets/images/uploaded/blog/create-blazor-app-7.webp)

### Use component param to serve data before render component
Before render Blazor WebAssembly component, we can pass param to it to serve data, it make component has data already before the component rendered.
In the `HomeController`, change Index method like this:
```csharp
    public IActionResult Index()
    {
        return View(weatherForecasts.ToArray());
    }
```
And this code in **FetchData** component and comment `OnAfterRenderAsync` method:
```csharp
    private WeatherForecast[]? forecasts;

    [Parameter] public WeatherForecast[]? initForecasts { get; set; }
    protected override void OnInitialized()
    {
        forecasts = initForecasts;
    }

    //protected override async Task OnAfterRenderAsync(bool firstRender)
    //{
    //    if (firstRender)
    //    {
    //        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("Home/WeatherForecasts");
    //        //force view update
    //        StateHasChanged();
    //    }
    //}
```
Now, pass data to component by component param:
```html
@using BlazorApp.Models;
@model WeatherForecast[]

<component type="typeof(BlazorApp.Pages.FetchData)" render-mode="WebAssemblyPrerendered" param-initForecasts=@Model></component>
```
Run the application and we will see the data is loaded without the component call api get data.

### Add Antiforgery to more secure
We can use the [Antiforgery  in Asp.Net](https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-7.0) to application more secure.
First, add this code in ``Program.cs`` of .NET MVC to add Antiforgery and config it option:
```csharp
//add antiforgery use request header
builder.Services.AddAntiforgery(options =>
{
    options.HeaderName = "X-CSRF-TOKEN";
});
```
Add ``[AutoValidateAntiforgeryToken]`` attribute in which want method to apply Antiforgery:
```csharp
    [HttpPost]
    [AutoValidateAntiforgeryToken]
    public IActionResult WeatherForecasts([FromBody] WeatherForecast model)
    {
        if (ModelState.IsValid)
        {
            model.Id = Guid.NewGuid().ToString();
            weatherForecasts.Add(model);
        }
        return Json(weatherForecasts);
    }
```
Declare ``antiRequestToken`` in **FetchData** component:
```csharp
[Parameter] public required string antiRequestToken { get; set; }
```
Use ``antiRequestToken`` in request:
```csharp
    async Task OnCreateFormSubmit()
    {
        //remove if existed and add anti token in request header
        Http.DefaultRequestHeaders.Remove("X-CSRF-TOKEN");
        Http.DefaultRequestHeaders.TryAddWithoutValidation("X-CSRF-TOKEN", antiRequestToken);

        using var createReq = await Http.PostAsJsonAsync("Home/WeatherForecasts", createModel);
        createReq.EnsureSuccessStatusCode();
        forecasts = await createReq.Content.ReadFromJsonAsync<WeatherForecast[]>();
    }
```
Pass anti token from view to component:
```html
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery antiforgery

@{
    ViewData["Title"] = "Home Page";
    string requestToken = antiforgery.GetAndStoreTokens(Context).RequestToken!;
}

<component type="typeof(BlazorApp.Pages.FetchData)" render-mode="WebAssemblyPrerendered" param-initForecasts=@Model param-antiRequestToken="@requestToken"></component>
```
Done, run the application and see the Antiforgery working now.

## Conclusion

In conclusion, this blog post has shown how to combine Blazor WebAssembly and .NET MVC application in a single project. This approach allows us to use the benefits of both technologies, such as the performance and interactivity of Blazor and the flexibility and compatibility of MVC. We have seen how to create a new Blazor WebAssembly project and reference it in the existing MVC project, how to configure the routing and hosting for both frameworks, and how to use Blazor components in MVC views and vice versa. We have also learned how to use some of the services provided by Blazor, such as the [IJSRuntime](https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/?view=aspnetcore-7.0) or the [state management](https://learn.microsoft.com/en-us/aspnet/core/blazor/state-management?view=aspnetcore-7.0&pivots=webassembly). 

I hope you have found this blog post useful and interesting please share it with your friends. Thank you for reading.

This post use the Visual Studio 2022 and .NET 7.0, use other version may not working. You can find full post and source code of this sample in [here](https://freemailforward.net/combining-blazor-webassembly-and-net-mvc-application-part-2).