0

I am learning ASP Net Core 2.2 MVC. I have read several articles regarding passing data from controller to view and vice-versa. At one point I wanted to pass more than 1 model to the view.

Then I realized that I cannot, and have to use what is called a View Model. I came up with this:

My Domain Models:

Blog.cs:

A blog can have many categories, all the other properties are the usual title, body etc.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Blogspot.Datas.Models
{
    public class Blog
    {
        [Key]
        public int id { get; set; }

        [Required]
        [DataType(DataType.Text)]
        public string title { get; set; }

        [Required]
        [DataType(DataType.Text)]
        public string body { get; set; }

        [DataType(DataType.DateTime)]
        public DateTime created_at { get; set; }

        [Column(TypeName = "boolean")]
        public bool comments { get; set; }

        public List<Category> categories { get; set; }
    }
}

Category.cs:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Blogspot.Datas.Models
{
    public class Category
    {
        [Key]
        public int id { get; set; }
        [Required]
        public string title { get; set; }

        public int blog_id { get; set; }
        [ForeignKey("blog_id")]
        public Blog blog { get; set; }
    }
}

In one of my view - Info.cshtml, I want to show a blog with its categories.

InfoViewModel.cs:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Blogspot.Datas.Models;

namespace Blogspot.Datas.Models.Pages
{
    public class InfoViewModel
    {
        public InfoViewModel()
        {
            this.categories = new List<Category>();
            this.category = new Category();
        }
        public int id { get; set; }

        [Required]
        public string title { get; set; }

        [Required]
        public string body { get; set; }

        [Required]
        public Category category { get; set; }
        public List<Category> categories { get; set; }
    }
}

Info.cshtml:

It shows the title and body of the blog, an its categories. I can also add a category (in the modal form).

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers


@model Blogspot.Datas.Models.Pages.InfoViewModel


<section class="infos">
    <form action="#">
        <input type="hidden" asp-for="@Model.id">

        <div class="form-group">
            <label for="title">Title</label>
            <input class="form-control" type="text" asp-for="@Model.title">            
        </div>

        <div class="form-group">
            <label for="body">Body</label>
            <input class="form-control" type="text" asp-for="@Model.body">
        </div>
    </form>

        <div class="categories">
            <h3>Categories
                <button type="button" style="float: right" class="btn btn-primary add-category">Add category</button>
            </h3>
            @foreach (var c in @Model.categories)
            {
                <div class="cat">
                    <p>@c.title</p>
                    <form asp-route="deleteCategory" asp-route-id="@c.id">
                        <button type="submit" class="btn btn-danger">Delete</button>
                    </form>
                    <hr>
                </div>
            } 
        </div>
</section>

<div class="modal fade category" tabindex="-1" role="dialog">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Modal title</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <form asp-route="storeCategory" method="post" asp-anti-forgery="true">
            <div class="form-group">
                <label asp-for="@Model.category.title">Title</label>
                <input class="form-control" type="text" asp-for="@Model.category.title">
                <span class="text-danger" asp-validation-for="@Model.category.title"></span>
            </div>
            <input type="hidden" asp-for="@Model.category.blog_id" value="@Model.id">
            <input type="submit" value="Save category" class="btn btn-success">
        </form>
      </div>
    </div>
  </div>
</div>

Now what has got me thinking is what would be the correct way of passing a parameter to the POST store function?

[HttpPost("categories", Name="storeCategory")]
    [ExportModelState]
    public async Task<IActionResult> storeCategory(Category category)
    {
        if (ModelState.IsValid)
        {
            await _context.category.AddAsync(category);
            await _context.SaveChangesAsync();    

            TempData["success"] = true;
            TempData["message"] = "Category added succesfully!";
        }

        return RedirectToRoute("postDetails", new { id = category.blog_id });

    }

What I've done is pass in the Category Domain Model. I saw articles which said that it should be View Model that gets passed, because its not a good practice to pass around Domain Models. Now my function works perfectly, but in the instance I pass a View Model, like storeCategory(InfoViewModel infoViewModel) wouldn't the other properties id, title, property, categories be redundant? Because all I need for that function is a category object.

Please enlightened me all of this patterns and conventions used.

Thank you.

3
  • I'd say go for Domain Model, Input Model and View Model. Commented Jan 22, 2019 at 11:54
  • Sorry, can you elaborate your comment more? Maybe the steps of going one to the other? The differences? Code example? Commented Jan 22, 2019 at 11:55
  • ViewModel to send data from app to view where you may add additional 'things' like e.g options for select List. Input model for sending data from view via e.g form or json. Difference is in properties that you gonna use.and or sending additional data or not sending redundant data Commented Jan 22, 2019 at 12:20

1 Answer 1

1

This is an opinionated question so here is my opinionated answer.

You should follow these principles and conventions if:

  1. The project you are building is for your own practice. (Practising good practices h3h3)
  2. The project you are making is going to be maintained by someone else. (The project will be easier to maintain in the future)
  3. The project is massive. (The structure will be cleaner, easier to maintain)

You shouldn't worry about this sort of thing if:

  1. This is a 1 time throw away project. (You'll quickly knock it up use it for a bit and throw it away, in this case you value time above anything else)

Now to specifically answer your case of displaying the Domain Model in the View. Why is this a bad thing?

When working with Objects it's important to know their place in the program (Where do I put my code?). Why not just create a single object with 100 fields and just use it in every view/method, because you are going to forget what's the context of methods and what they are meant to be doing and which fields belong where. As well as having an object like DataModel.cs it's a data model we know but what does it represent? what's the context? what is this program about? So now you might name is BlogPost.cs to bring clarity.

Single responsibility is key, an object/class/function is responsible only for 1 thing and 1 thing only.

  • BlogPost - Blog post DTO.
  • BlogPostViewModel - The data that we would like to display to the users.
  • BlogPostInputModel - The data we would like to capture to create this Blog post.
  • CreateBlogPost(BlogPostInputModel im) - Create BlogPost from BlogPostInputModel
  • SaveBlogPost(BlogPost bp) - Save BlogPost to the database.

I hope you understand that this extra work is done to produce self documenting code.

If you need to display BlogPostViewModel but capture only BlogPostInputModel it's fine that BlogPostViewModel has some properties that BlogPostInputModel because we need them for the view.

Update, further explanations:

CreateBlogPost(BlogPostInputModel im) - This is a pure function it takes and input A and spits out B, pure means there are no side effects and isn't affected by state. So saying that if a function depended on state like time if we supplied A it might spit out C or F depending on what time it is. This way it's easier to test and it always returns a valid BlogPost.

SaveBlogPost(BlogPost bp) - This is a function with a side effect which is: writing to a database. It just consumes a valid BlogPost and saves it to the database. This sort of function would be in your repository, essentially all your state management is contained in a Repository object.

If we would save the BlogPost to the database inside CreateBlogPost, if we would write a test, it would need to seed and then revert the changes in the database for every test. This is problematic.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for answering. Could you maybe show an example code? What is the correlation between CreateBlogPost(BlogPostInputModel im) and SaveBlogPost(BlogPost bp)? Oh and on a side note I am going to watch your tutorials :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.