简体   繁体   中英

Html.DisplayFor interface viewmodel in ASP.NET Core 2

I'm having a hard time figuring out how to render a display template for an interface type viewmodel. Assume that I have a viewmodel like the one below:

public class DashboardViewModel
{
    public ISelectedWalletsViewModel Wallets { get; set; }
}

public interface ISelectedWalletsViewModel
{
}

public class OneSelectedWalletViewModel : ISelectedWalletsViewModel
{
    public SelectedWalletViewModel SelectedWallet { get; set; }
    public IEnumerable<WalletViewModel> OtherWallets { get; set; }
}

public class AllSelectedWalletsViewModel : ISelectedWalletsViewModel
{
    public IEnumerable<WalletViewModel> Wallets { get; set; }
}

public interface IWalletViewModel
{
    long Id { get; set; }
    string Name { get; set; }
    string Description { get; set; }
    string CurrentBalance { get; set; }
}

public class WalletViewModel : IWalletViewModel
{
    public long Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public string CurrentBalance { get; set; }
}

public class SelectedWalletViewModel : IWalletViewModel
{
    public long Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public string CurrentBalance { get; set; }
}

So I created the DisplayTemplates folder to set up the corresponding .cshtml files for each and every model:

<!-- START OneSelectedWalletViewModel.cshtml -->
@using CLSoft.MyWallet.Models.Home
@model OneSelectedWalletViewModel

@Html.DisplayFor(m => m.SelectedWallet)
@Html.DisplayFor(m => m.OtherWallets)
<a asp-action="Index">Select all wallets</a>
<!-- END OneSelectedWalletViewModel.cshtml -->

<!-- START AllSelectedWalletsViewModel.cshtml -->
@using CLSoft.MyWallet.Models.Home
@model AllSelectedWalletsViewModel

@Html.DisplayFor(m => m.Wallets)
<!-- END AllSelectedWalletsViewModel.cshtml -->

<!-- START SelectedWalletViewModel.cshtml -->
@using CLSoft.MyWallet.Models.Home
@model SelectedWalletViewModel

<div class="card border-primary">
    <div class="card-body">
        <h5 class="card-title">@Model.Name</h5>
        <h6 class="card-subtitle mb-2 text-muted">@Model.CurrentBalance</h6>
        <p class="card-text">@Model.Description</p>
    </div>
</div>
<!-- END SelectedWalletViewModel.cshtml -->

<!-- START WalletViewModel.cshtml -->
@using CLSoft.MyWallet.Models.Home
@model WalletViewModel

<div class="card">
    <div class="card-body">
        <h5 class="card-title">@Model.Name</h5>
        <h6 class="card-subtitle mb-2 text-muted">@Model.CurrentBalance</h6>
        <p class="card-text">@Model.Description</p>
        <a asp-action="Index" asp-route-wallet-id="@Model.Id">select wallet</a>
    </div>
</div>
<!-- END WalletViewModel.cshtml -->

Now I'm missing just the controller action method:

[HttpGet]
public async Task<IActionResult> Index(long? walletId)
{
    var viewModel = await _service.GetDashboardViewModelAsync(walletId);
    return View(viewModel);
}

and the Index.cshtml view code:

@using CLSoft.MyWallet.Models.Home
@model DashboardViewModel

@{
    ViewData["Title"] = "Index";
}

<div class="row">
    <div class="col">
        <h4>Your wallets</h4>
        @Html.DisplayFor(m => m.Wallets)
    </div>
</div>

And now I should be all set up. The thing is no markup is rendered. During my tests I tried to add a display template for the interface ISelectedWalletsViewModel

@model ISelectedWalletsViewModel
@Html.DisplayForModel()

put a breakpoint on @Html.DisplayForModel() and - ta dah! - the debugger stopped as it should. But, alas, no concrete DisplayTemplate was invoked. Am I missing something?

EDIT 1 : I created a github repo to show the problem.

EDIT 2 : I also reported the issue on AspNet/Mvc repository

Turns out this is by design . I'll quote the answer by NTaylorMullen below, in case the link would die (unlikely, but still):

[...] So in regards to this issue it's actually by design. When moving to Core we found that in legacy ASP.NET we had a bad habit of oversharing (using view model templates that were unintended). To combat this we removed that capability and required users to be explicit in the types that they use when working with display templates. Sorry I don't have a better answer for you!

So, how should you refactor your code to meet these rules?

  1. [not so good] you could refactor your ViewModels to a single one which will contain all the possible implementations and use if statements inside Views' code to render the desired one;
  2. [better] you could refactor your controller action logic to handle all the possible implementations of your ViewModel, and provide the desired View.

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