ASP.NET Core 2.2 For Beginners (Part 6): Adding a Create View

Adding a Create View

When creating a new record in the data source with a Create view, you have to implement two action methods. The first is a method using HTTP GET to render the Create view in the browser, filling select lists and other controls that need data. The second method is an HTTP POST method that receives data from the client through an HTTP POST request.

The post from the client can be done in several ways, for instance with JavaScript or a form post. In this example, you will use a form post to call back to the server when the user clicks a Submit button.

The HTTP POST action method can fetch data from several places in the posted data: the header, the query string, and the body of the request. The data is then matched against properties in a model object, which is a parameter of the action method. The action can also handle simple types such as int and string, without them being encapsulated in a model object.

There is a naming convention that you need to be aware of, to properly match posted form data with properties in model objects and other parameters. The rule states that the element names in the form data must match the property names to be matched.

The default behavior of a view using an enum is to display it as a text field. This is not the best way to display a selected item in a list of values. In this section, you will remove the Video class’s GenreId property, and add a new property of the enum type Genres called Genre. This makes it easier to work with enum data, especially when working with a SQL Server database entity model.

You will also add the enum as a property to a new view model called VideoCreateEdit­ViewModel, which can be used both when creating a new video and when editing one.

Refactoring the Application

  1. Open the Video
  2. Delete the GenreId
  3. Add a using statement to the Models namespace where the Genre enumeration is located.

using AspNetCore22Intro.Models;

  1. Add a new property of type Genres and name it Genre. This property will hold the current genre for the video.

public Genres Genre { get; set; }

  1. Open the MockVideoData
  2. Replace the GenreId property with the Genre property and assign its value from the enum

new Video { Id = 1, Genre = Models.Genres.Animated, Title = "Shreck" },

  1. Open the HomeController
  2. Locate the Index action and change the assignment of the Genre string in the VideoViewModel object to use the value stored in the Genre property of the Video You can use the ToString method to fetch the name of the enum value.

Genre = video.Genre.ToString()

  1. Repeat step 7 for the Details action method but use the model variable instead of the video
  2. Switch to the browser and refresh the application. It should look and work the same as before.

The complete code for the Video class, after the changes:

public class Video
{
    public int Id { get; set; }
    public string Title { get; set; }
    public Genres Genre { get; set; }
}

 

The complete code for the MockVideoData constructor, after the changes:

public MockVideoData()
{
    _videos = new List<Video>
    {
        new Video { Id = 1, Genre = Models.Genres.Animated,
            Title = "Shreck" },

        new Video { Id = 2, Genre = Models.Genres.Animated,
            Title = "Despicable Me" },

        new Video { Id = 3, Genre = Models.Genres.Animated,
            Title = "Megamind" }
    };
}

 

The complete HomeController class after the changes:

public class HomeController : Controller
{
    private readonly IVideoData _videos;

    public HomeController(IVideoData videos)
    {
        _videos = videos;
    }

    public ViewResult Index()
    {
        var model = _videos.GetAll().Select(video =>
            new VideoViewModel
            {
                Id = video.Id,
                Title = video.Title,
                Genre = video.Genre.ToString()
            });

        return View(model);
    }

    public IActionResult Details(int id)
    {
        var model = _videos.Get(id);

        if (model == null) return RedirectToAction("Index");

        return View(new VideoViewModel
        {
            Id = model.Id,
            Title = model.Title,
            Genre = model.Genre.ToString()
        });
    }
}

Adding the HTTP GET Create Action and the Create View

The HTTP GET Create action method renders the Create view to the browser, displaying the necessary controls to create a new video and to post the form to the server.

  1. Open the HomeController
  2. Add a new action method called Create, with the return type IActionResult. Return the View

public IActionResult Create()
{
    return View();
}

  1. Right click on the Create action name and select Add View. Click the Add button to create the Razor View to the Views/Home
  2. Add a @using statement to the Models folder at the top of the view, to get access to the enum definition for the select list.

@using AspNetCore22Intro.Models

  1. Add an @model directive with the Video class as the view model.

@model AspNetCore22Intro.Entities.Video

  1. To be able to use Tag Helpers and the asp- attributes, which is the new way to add ASP.NET specific markup to views, you have to add a @addTagHelper directive to the view, or a shared file. You will learn more about Tag Helpers later.

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

  1. Change the text in the <h1> to Create Video.
  2. Add a <form> element and use the asp-action attribute to specify the action to post to when the Submit button is clicked. Make the form post to the server by assigning post to the method

<form asp-action="Create" method="post">

  1. Add a table with two rows to the form, one for the Title and one for the Genre enum.
  2. Use the asp-for attribute to specify which property the controls should bind to. Add a <label> and an <input> element for the Title

<tr>
    <td><label asp-for="Title"></label></td>
    <td><input asp-for="Title"/></td>
</tr>

  1. Use the same attribute when you add the <label> and <select> elements for the Genre enum. To list the enum items, you must add the asp-items attribute to the <select> element and call the GetEnumSelectList method on the Html

<tr>
    <td><label asp-for="Genre"></label></td>
    <td><select asp-for="Genre"
        asp-items="Html.GetEnumSelectList<Genres>()"></select>
    </td>
</tr>

  1. Add a submit button with the text Create to the form.

<input type="submit" value="Create" />

  1. Add an anchor tag with the text Back to List below the form. Use the asp-action attribute to specify that the link should navigate to the Index

<a asp-action="Index">Back to List</a>

  1. Save all files and switch to the browser. Navigate to the /Home/Create You should see a form with a text field, a drop-down with all genres listed, a Submit button, and a link leading back to the Index view. The Submit button won’t work yet, because you haven’t added the required action method.
  2. Click the link to navigate to the Index

The complete code for the HTTP GET Create action:

public IActionResult Create()
{
    return View();
}

 

The complete markup for the Create view:

@using AspNetCore22Intro.Models
@model AspNetCore22Intro.Entities.Video
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    ViewData["Title"] = "Create";
}

<h2>Create Video</h2>

<form asp-action="Create" method="post">
    <table>
        <tr>
            <td><label asp-for="Title"></label></td>
            <td><input asp-for="Title" /></td>
        </tr>
        <tr>
            <td><label asp-for="Genre"></label></td>
            <td><select asp-for="Genre"
                asp-items="Html.GetEnumSelectList<Genres>()"></select>
            </td>
        </tr>
    </table>

    <input type="submit" value="Create" />
</form>

<div>
    <a asp-action="Index">Back to List</a>
</div>

Adding the VideoCreateEditViewModel Class

This view model will be used when the controller receives a post from a video’s Edit or Create view.

  1. Create a new class called VideoCreateEditViewModel in the ViewModels
  2. Add an int property named Id and a string property named Title.
  3. Add a using statement to the Models namespace to get access to the Genre

using AspNetCore22Intro.Models;

  1. Add a property called Genre of type Genres. This property will contain the genre selected in the form when the submit button is clicked, and a post is made back to the controller on the server.

public Genres Genre { get; set; }

 

The complete code for the VideoCreateEditViewModel class:

public class VideoCreateEditViewModel
{
    public int Id { get; set; }
    public string Title { get; set; }
    public Genres Genre { get; set; }
}

Adding the HTTP POST Create Action

A <form> element is used when a user should enter data in a view. There are a few steps that are performed when a user posts data. The first you already know: the user sends an HTTP request to the HTTP GET action in the controller, which fetches the necessary data after which the view is rendered.

To handle the form’s post back to the server, an HTTP POST version of the action method is called with the form values. The names of the form controls are matched against the model properties or parameters available in the action’s parameter list.

The POST action then uses that data to create, update, or delete data in the data source.

When passing data from the view to the action, MVC will, by default, match all properties in the form with properties in the model. This can be risky, especially if you use an entity class as the model. In many scenarios, you don’t want to receive all data the form sends to the action. So how do you tell MVC to use only the values of interest? You create a separate view model, like you did in the previous section.

Let’s implement the HTTP POST Create action in the HomeController class.

  1. Open the HomeController
  2. Add a new action method called Create that takes the VideoCreateEditViewModel as a parameter named model.

public IActionResult Create(VideoCreateEditViewModel model) {
    return View();
}

  1. Save all files and switch to the browser. Navigate to the /Home/Create You should see an error message telling you that multiple actions were found with the same name.
  2. To fix this, you need to decorate the GET action with the [HttpGet] attribute, and the POST action with the [HttpPost] This will tell ASP.NET which method to call when the view is rendered and which method to call by the client when posting data.

[HttpGet]
public IActionResult Create()
{
    return View();
}

[HttpPost]
public IActionResult Create(VideoCreateEditViewModel model)
{
    return View();
}

  1. Place a breakpoint on the return statement in the POST action.
  2. Save all files and start the application with debugging (F5). Navigate to the /Home/Create The Create view should be displayed again.
  3. Fill out the form and click the Create The execution should halt at the breakpoint, proving that the Create button posts to the server. Inspect the content in the model object; it should contain the values you entered in the form.

  4. Stop the application in Visual Studio.
  5. Add a using statement to the Entities namespace to get access to the Video entity class in the HomeController

using AspNetCore22Intro.Entities;

  1. Because the purpose of the HttpPost Create method is to add a new video to the data source, you will have to create an instance of the Video class and assign values to it from the model object properties. Note that you don’t have to assign the Id The video doesn’t exist in the data source yet, and therefore doesn’t have an id.

var video = new Video
{
    Title = model.Title,
    Genre = model.Genre
};

  1. Because you have implemented the IVideoData Interface as a service that is injected into the constructor, you have to add an Add method to it, and implement it in the MockVideoData This will make it possible to call the Add method on the _videos variable to add a new video. Let’s implement it one step at a time. Begin by opening the IVideoData Interface.
  2. Add a new void method called Add that takes a Video parameter called newVideo.

void Add(Video newVideo);

  1. Add the method to the MockVideoData You can use the light bulb button if you hover over the interface name.
  2. Remove the throw statement from the method.
  3. Because the data source is a collection that is unable to generate new ids, you have to create a new id for the video object. You can fake an id by using LINQs Max method to fetch the highest id and add 1 to it. This id is only used for demo purposes and should never be used in a production environment where the id is created automatically by the database.

newVideo.Id = _videos.Max(v => v.Id) + 1;

  1. To add the new video to the _videos collection, you must change its data type to List. You can’t add values to an IEnumerable To preserve the values between HTTP requests, you will later change the scope of the IVideoData service in the Startup class.

private readonly List<Video> _videos;

  1. Make a call to the Add method on the _videos collection, to add the new video in the Add method you created in the MockVideoData

public void Add(Video newVideo)
{
    newVideo.Id = _videos.Max(v => v.Id) + 1;
    _videos.Add(newVideo);
}

  1. Open the HomeController class and call the Add method you just created, from the HTTP POST Create action, and pass in the video object to it.

_videos.Add(video);

  1. To prevent the user from submitting the Create form multiple times by refreshing the page, you must replace the View method with a call to the RedirectToAction method and redirect them to another view, like the Details Because the Details view has an id parameter you must pass in the name of the view, and the video id wrapped in an anonymous object.

return RedirectToAction("Details", new { id = video.Id });

  1. Open the Startup class and locate the ConfigureServices Change the scope of the IVideoData service to singleton by calling the AddSingleton method instead of the AddScoped that is currently used. You do this to preserve the data between HTTP requests.

services.AddSingleton<IVideoData, MockVideoData>();

  1. Save all the files and navigate to the /Home/Create Fill out the form and click the Create button. Instead of remaining on the Create view, you are redirected to the Details view, which displays the added video. Note that the URL changed to /Home/Details/4 with the redirect.

The complete code for the IVideoData interface:

public interface IVideoData
{
    IEnumerable<Video> GetAll();
    Video Get(int id);
    void Add(Video newVideo);
}

 

The complete code for the Add method in the MockVideoData class:

public void Add(Video newVideo)
{
    newVideo.Id = _videos.Max(v => v.Id) + 1;
    _videos.Add(newVideo);
}

 

The complete code for the Create actions:

[HttpGet]
public IActionResult Create()
{
    return View();
}

[HttpPost]
public IActionResult Create(VideoCreateEditViewModel model)
{
    var video = new Video
    {
        Title = model.Title,
        Genre = model.Genre
    };

    _videos.Add(video);

    return RedirectToAction("Details", new { id = video.Id });
}

 

The complete code for the ConfigureServices method in the Startup class:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddSingleton(provider => Configuration);
    services.AddSingleton<IMessageService, ConfigurationMessageService>();
    services.AddSingleton<IVideoData, MockVideoData>();
}

 

Stay connected with news and updates!

Join our mailing list to receive the latest news and updates from our team.
Don't worry, your information will not be shared.

Subscribe
Close

50% Complete

Two Step

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.