Asp.Net Core 2.0 - Passing data into a Model Using DI

June 24, 2018

Imagine you have an Asp.Net Core web page, and you would like to edit some data in a form, but you’d like to default that data to something (maybe initially specified in the Web.Config).

I’m sure there’s dozens of ways to achieve this; but this is one.

Let’s start with a bog standard MVC web app:

aspnetcore2 di 1

Step one is to define a model in which to hold your data; here’s mine:



public class CurrentAppData
{
    public string DataField1 { get; set; }
}

Let’s register that in the IoC container:



// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for non-essential cookies is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });
 
 
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version\_2\_1);
 
    services.AddTransient<CurrentAppData, CurrentAppData>(
        a => new CurrentAppData()
        {
            DataField1 = "test value"
        });

Next thing we’ll need is a View, and a corresponding view model to edit our data; here’s the view:



@model EditDataViewModel
@{
    ViewData["Title"] = "Edit Data";
}
 
<h2>@ViewData["Title"]</h2>
 
<div>
    <label>Change data here:</label>
    <input type="text" asp-for="EditData.DataField1" />
 
</div>

And now the view model (that is, the model that is bound to the view):



public class EditDataViewModel
{
    public EditDataViewModel(CurrentAppData editData)
    {
        EditData = editData;
    }
    public CurrentAppData EditData { get; set; }
}

The final step here is to adapt the controller so that the CurrentAppData object is passed through the controller:



public class EditDataController : Controller
{
    private readonly CurrentAppData \_currentAppData;
 
    public EditDataController(CurrentAppData currentAppData)
    {
        \_currentAppData = currentAppData;
    }
 
    public IActionResult EditData()
    {
        return View(new EditDataViewModel(\_currentAppData));
 
 
    }
}

That works as far as it goes, and we now have the data displayed on the screen:

aspnetcore2 di 2

The next step is to post the edited data back to the controller; let’s change the HTML slightly:

[code lang=“html”] <form asp-action=“UpdateData” asp-controller=“EditData” method=“post” enctype=“multipart/form-data”>          <input type=“text” asp-for=“EditData.DataField1” />     <br />     <button type=“submit” class=“btn-default”>Submit Changes




We've added a submit button, which should cause the surrounding form element to execute whichever "method" it's been told to (in this case, POST).  It will look for an action on the controller called UpdateData, so we should create one:



``` csharp


public IActionResult UpdateData(EditDataViewModel editDataViewModel)
{
    System.Diagnostics.Debug.WriteLine(editDataViewModel.EditData.DataField1);
    return View("EditData", editDataViewModel);
}

Here, we’re accepting the EditDataViewModel from the view. However; when we run this, we get the following error:

Error:

InvalidOperationException: Could not create an instance of type ‘WebApplication14.Models.EditDataViewModel’. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, give the ‘editDataViewModel’ parameter a non-null default value.

Let’s first implement a fix for this, and then go into the whys and wherefores. The fix is actually quite straightforward; simply give the view model a parameterless constructor:



public class EditDataViewModel
{
    public EditDataViewModel()
    {
        
    }
 
    public EditDataViewModel(CurrentAppData editData)
    {
        EditData = editData;
    }
    public CurrentAppData EditData { get; set; }
}

The problem that we had here is that the `EditDataViewModel` that is returned to UpdateData is a new instance of the model. We can prove this by changing our code slightly:

aspnetcore2 di 3

Here, we’ve added a field called TestField1 to the model, and populated it just before we pass the model to the view; and on the post back, it’s gone. I’m not completely sure why the view model can’t be created by the middleware in the same way that the controller is; but that’s the subject of another post.

Finally, show the value back to the screen

To wrap up, we’ll just show the same value back to the screen; we’ll add an extra value to the model:




public class CurrentAppData
{
    public string DataField1 { get; set; }
 
    public string DisplayField1 { get; set; }
}

And we’ll just display it in the view:

[code lang=“html”] <form asp-action=“UpdateData” asp-controller=“EditData” method=“post” enctype=“multipart/form-data”>          <input type=“text” asp-for=“EditData.DataField1” />     <br />     <button type=“submit” class=“btn-default”>Submit Changes     <br />          <br />




Finally, we'll copy that value inside the controller (obviously, this is simulating something meaningful happening), and then display the result:



``` csharp


public IActionResult UpdateData(EditDataViewModel editDataViewModel)
{
    editDataViewModel.EditData.DisplayField1 = editDataViewModel.EditData.DataField1;
    return View("EditData", editDataViewModel);
}

Let’s see what that looks like:



Profile picture

A blog about one man's journey through code… and some pictures of the Peak District
Twitter

© Paul Michaels 2024