Hi, since I've spectacularly failed to build a simple MVC application (for now at least ) https://www.daniweb.com/web-development/aspnet/threads/496278/building-first-mvc-application, I've decided to do even a simpler one, modelled on this one http://www.codeproject.com/Articles/683942/An-Absolute-Beginners-Tutorial-for-understanding-E , which is supposed to create a few books and their associated reviews
I don't think it gets simpler than that. Naturally I run into some issues, but this time it seems to have to do only with the views. Let's look at the code.
So, I created my classes, as always in two separate files:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace BooksApplication.Models
{
    public class Book
    {
        public int BookID { get; set; }
        public string BookName { get; set; }
        public string ISBN { get; set; }
        public virtual ICollection<Review> Reviews { get; set; } //1 book can have multiple reviews, 1 to many?

    }
}




using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BooksApplication.Models
{
    public class Review
    {
        public int ReviewID { get; set; }
        public string ReviewText { get; set; }
        public int BookID { get; set; }//foreign key
        public virtual Book Book { get; set; } // This apparently keeps track of the book this review belong to as ever
    }
}

The I created the usual things, 2 controllers (BookController.cs and ReviewController.cs) with read and write action using EF respectively associated with Book (BooksApplication.Models) and Review (BooksApplication.Models) model classes and both with the same data context class BooksApplicationContext (BooksApplication.Models), here it is:

using System.Data.Entity;

namespace BooksApplication.Models
{
    public class BooksApplicationContext : DbContext
    {
        // You can add custom code to this file. Changes will not be overwritten.
        // 
        // If you want Entity Framework to drop and regenerate your database
        // automatically whenever you change your model schema, add the following
        // code to the Application_Start method in your Global.asax file.
        // Note: this will destroy and re-create your database with every model change.
        // 
        // System.Data.Entity.Database.SetInitializer(new System.Data.Entity.DropCreateDatabaseIfModelChanges<BooksApplication.Models.BooksApplicationContext>());

        public BooksApplicationContext() : base("name=BooksApplicationContext")
        {
        }

        public DbSet<Book> Books { get; set; }
        public DbSet<Review> Reviews { get; set; }
    }
}

So, I run a build and then tested both controllers, everything works fine, in that I can create books and multiple reviews for a single book. The thing is though, what I want to do is to be able to navigate to a specific book detail page, say
http://localhost:xxxxxx/Book/Details/1 and see not only the book I created, like this http://s29.postimg.org/ggwvw4j87/book_details.jpg but also the review/s associated with it and currently I can only see the reviews if I navigate to http://localhost:XXXXXX/Review, like here http://s10.postimg.org/yo5s41trd/review_details.jpg
I hope it makes sense. So, I figured that it was just an amendment to carry out in the book details view, that is to call the model.Review.ReviewText and display it so I went in there and added these last two lines:

<div class="display-label"><!-- added by me, to view review label-->
    @Html.DisplayNameFor(model => model.Review.ReviewText)
</div>
<div class="display-field"><!-- added by me, to view review field-->
    @Html.DisplayFor(model => model.Review.ReviewText) 
</div>

Effectively trying to access the Review details view from the Book details view, but evidently I got it wrong because the Review in @Html.DisplayNameFor(model => model.Review.ReviewText) is underlined and if I hover on it, a tooltip says: "BooksApplication.Models.Book doesn't contain a definition for 'Review' and no extension method 'Review' accepting a first argument of type BooksApplication.Models.Book could be found (are you missing a using directive or an assembly reference?)"

Well, am I? Does it mean that somehow I have to reference the Review object inside the Bood details view? If so how?

Here is the full Book details view with my additions:

@model BooksApplication.Models.Book

@{
    ViewBag.Title = "Details";
}

<h2>Details</h2>

<fieldset>
    <legend>Book</legend>

    <div class="display-label">
         @Html.DisplayNameFor(model => model.BookName)
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.BookName)
    </div>

    <div class="display-label">
         @Html.DisplayNameFor(model => model.ISBN)
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.ISBN)
    </div>
    <div class="display-label"><!-- added by me, to view review label-->
       @Html.DisplayNameFor(model => model.Review.ReviewText)
    </div>
    <div class="display-field"><!-- added by me, to view review field-->
       @Html.DisplayFor(model => model.Review.ReviewText) 
    </div>
</fieldset>
<p>
    @Html.ActionLink("Edit", "Edit", new { id=Model.BookID }) |
    @Html.ActionLink("Back to List", "Index")
</p>

Any idea?

Recommended Answers

All 6 Replies

Hi

If I understand correctly, you are trying to grab a review directly from the model which is of type Book. This won't work as the class Book does not have a Review property. It does however have a Reviews property which is a collection, so you will need to enumerate this to get access to each review associated with the book.

For example, add the following to your Book Details view:

    <p><b>Reviews</b></p>
    @foreach (var review in Model.Reviews)
    {
        <p>@review.ReviewText</p>
    }

Now you will get each review for that particular book.

HTH

Hi djjeavons, yes that's exactly what I meant: I added you code and it works OK. Let me see if I get things right. You said that the Book model has a collection of review properties, is that because the relationship between the Book and the Review is 1 to many? If I look at the Review details view instead, I can see these two lines:

<div class="display-label">
    @Html.DisplayNameFor(model => model.Book.BookName)
</div>
<div class="display-field">
    @Html.DisplayFor(model => model.Book.BookName)
</div>

So it's essentially doing what I was trying to do with the Book view, but I presume this is allowed here because the Review view has a Book property which isn't a collection, and that's because the review/s can only refer to one book?
Also, can I ask you, are different views somehow connected, in the sense we're accessing one view from the other, why are we allowed to do that? Happy to do some readings, but I've never come across this "link" between them
thanks

You said that the Book model has a collection of review properties, is that because the relationship between the Book and the Review is 1 to many?

Yes, one book can have many reviews. This is possible due to the property Reviews (which is a collection of the Review class) that you have in the Book class. If you open Server Explorer and look at the columns for your two tables, you will see that the Reviews table has a column called BookID which acts as a foreign key to your Books table. You can see this if you create a new query against your database:

SELECT Books.BookID, Books.BookName, Books.ISBN, Reviews.ReviewID, Reviews.ReviewText, Reviews.BookID FROM Books INNER JOIN Reviews ON Books.BookID=Reviews.BookID

Your second point regarding the details view for the Review acting the way you imagined is correct. The Review class has a property of type Book that is not a collection so it only refers to one instance. Hence you can use model.Book.BookName.

Also, can I ask you, are different views somehow connected, in the sense we're accessing one view from the other, why are we allowed to do that?

I'm not sure I totally understand the question. Views themselves are not connected with other views, however the data you provide to a view (i.e. what the view is strongly typed to) may contain data from different sources. In your example at the moment, the Book Details view takes data from both the Book model and the Reviews model as you have the Reviews property. Likewise, the Review Details view is the same due to the Book property.

There is another concept called ViewModels. Consider that you have a large application that deals with Sales data for example. You would probably have models for budget, forecast, actuals and so on. Each of these models may well have their own Views and Controllers for managing the data, however, what if you wanted to create a Dashboard type page? There are many ways to do this, but one approach for supplying the data to this type of view would be to create a ViewModel which has properties for each of your models that you want to display. The ViewModel itself is fairly straightforward and simply consists of properties for each model, something like:

public class DashboardViewModel
{
    public BudgetModel budget { get; set; }
    public ActualModel actual { get; set; }
    public ForecastModel forecast { get; set; }
}

Now you could create a View based on this DashboardViewModel and you would then have access to three models from one.

Not sure if that answers your question?

Thanks djjeavons.
Yes I had a look at the tables as you suggested, table data and table definition.

The view model is interesting actually, but what I meant - and sorry for not being able to articulate that correctly, but I kind of have a lot of gaps that need to be filled evidently :-), is something slightly different. You said:

the Book Details view takes data from both the Book model and the Reviews model as you have the Reviews property. Likewise, the Review Details view is the same due to the Book property.

How does that exactly happen? So, let's take my Book Details view again, which if I'm not mistaken, is a strongly typed view, due to this @model BooksApplication.Models.Book: which one is the reviews property you're referring to, where is it, other than the one declared in the classes?

which one is the reviews property you're referring to, where is it, other than the one declared in the classes?

I think I see where the confusion is. Yes, your view is strongly typed to the Book class, so within the view you can only reference a Book model. However, your Book class has a property which is a collection of Reviews, so you have access to those Reviews based on this property. So whereas model.BookName will display the name of the book, model.Reviews[0].ReviewText will display the text of the first review in the Reviews collection of the Book model.

Perfect, that's it! Thank you very much for clarifying that!

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.