简体   繁体   中英

Abstracting views in MVC using inheritance in viewmodels

I need some advice on how exactly to proceed in this scenario. Say I have a view which renders one child partial and one grandchild partial view. One partial view is called _CommentsContainer while the other is called _Comment . _CommentsContainer is the child which iterates through an IPagedList<IComment> and spits out a _Comment for each viewmodel.

/// <summary>
/// Represents an object that is able to hold comments or can be commented on. E.G. a photo, a video, a bulletin
/// , or even another comment
/// </summary>
public interface ICommentable
{
   IPagedList<IComment> Comments { get; set; }
}  


public interface IComment
{
    Guid Id { get; set; }
    string CommentingUsername { get; set; }
    Guid CommentingUserId { get; set; }
    DateTime? PostDate { get; set; }
    string Content { get; set; }

    Guid? InResposeToCommentId { get; set; }
}  

If I want to have a big switch statement in my _CommentsContainer how can I account for viewmodels that inherit this ICommentable but which implement their own set of properties that are not part of the interface it inherits from?

@model Jdmxchange_V3.Models.Abstractions.Interfaces.ICommentable
@if (Model.GetType() == typeof(Jdmxchange_V3.Models.BulletinViewModels.BulletinDetailsViewModel))
{
<div class="row">
    <div class="panel panel-default recent-listings">
        <div class="panel-heading">Conversation</div>
        <div class="panel-body">
            @foreach (var comment in Model.Comments)
            {
                @Html.Partial("_Comment", comment)
            }
        </div>
    </div>
</div>
}

else if (Model.GetType() == typeof(Jdmxchange_V3.Models.EventDetailsViewModel))
{
<div class="row">
    <div class="panel panel-default recent-listings">
        <div class="panel-heading">Conversation @Model.EventSubTitle@*<< this property is not in ICommentable*@ </div>
        <div class="panel-body">
            @foreach (var comment in Model.Comments)
            {
                @Html.Partial("_Comment", comment)
            }
        </div>
    </div>
</div>
}  

As you can see, that last else if is referencing a property not found on ICommentable . I could simply add this property to ICommentable and make it required for all derived classes but what if I don't need it on every derived class and only need it on one class? My question is

In this scenario, how can I offer some type of generic type in my partial view, check it's type, and reference properties of that specific type?

My initial thought is this can't be done, I have never seen it done nor can I think of a similar situation where it can be done.

I would not use this method at all. Instead, I would use editor/display templates, as they work on the actual type of the model/property rather than what is defined in the @model statement.

For example, you could create two tempates here:

~/Views/Shared/DisplayTemplates/BulletinDetailsViewModel.cshtml

@model BulletinViewModels.BulletinDetailsViewModel
<div class="row">
    <div class="panel panel-default recent-listings">
        <div class="panel-heading">Conversation</div>
        <div class="panel-body">
            @foreach (var comment in Model.Comments)
            {
                @Html.Partial("_Comment", comment)
            }
        </div>
    </div>
</div>

~/Views/Shared/DisplayTemplates/EventDetailsViewModel.cshtml

@model BulletinViewModels.EventDetailsViewModel
<div class="row">
    <div class="panel panel-default recent-listings">
        <div class="panel-heading">Conversation @Model.EventSubTitle@</div>
        <div class="panel-body">
            @foreach (var comment in Model.Comments)
            {
                @Html.Partial("_Comment", comment)
            }
        </div>
    </div>
</div>

Then you just have in your main view:

@model Jdmxchange_V3.Models.Abstractions.Interfaces.ICommentable

@Html.DisplayForModel()

or you could use

@Html.DisplayFor(m => m)

You could also use editor templates, rather than partials, for your comments. EditorTemplates automatically iterate over IEnumerables, so you would only need to do this:

@Html.DisplayFor(m => m.Comments)

Unfortunately, there is no switch statement for types. Your best bet is to compare types like you're doing, although it might be more readable to do it with an is comparison and take advantage of polymorphism, like this:

@using Jdmxchange_V3.Models
@if (Model is BulletinViewModels.BulletinDetailsViewModel)
{
    var bdvm = Model as BulletinViewModels.BulletinDetailsViewModel;
    // do things specific to this type
}
else if(Model is EventDetailsViewModel)
{
    var edvm = Model as EventDetailsViewModel;
    // do things specific to this type
}

You didn't ask for this bit, but I'll put it here anyway: I would suggest you may not really need the grandchild partial. You could save yourself some allocations (and garbage collection load) by eliminating the grandchild view and making the _CommentsContainer partial view render everything. If your site doesn't have many users or long comment threads, then it may not matter for performance and you should carry on as you are.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM