0

I'm new to MVC and I need to bright up my mind about some concept of ASP .NET MVC.

In my spare time I'm trying to learn more about MVC building a web application to manage some user content. For more details, you can see my previous question about the same application here.

Now I'm stuck on a controller action that should pass a view model to a view and I need to know a couple o things. So I'd like to know if and where I'm doing wrong.

1) In the PortafoglioIndexViewModel I added a User property to get the Username inside the view. In the View I call Model.Username to show the username on the page. It works but since I remove the IEnumerable<> from the @model directive I cannot iterate through the object anymore. Intellisense markup in red my code @Html.DisplayNameFor(model => model...) and @Html.DisplayFor(modelItem => item...) inside the foreach loop.

If I change @model with IEnumerable<FundMonitor.Web.ViewModels.PortafoglioIndexViewModel>, then I can iterate through object but if I call Model.Username I no longer get the object properties since it is an IEnumerable. So in this case the only way I found to get the Username property is to add @using FundMonitor.Web.ViewModels at the top of the view and then make a cast like ((PortafoglioIndexViewModel)Model).Username but I don't think that would be a best practice...

So how can I get goat and cabbage?

2) Is my way of populating and generating the View Model inside the Index action right?

3) What would be a better way to get the username/userid since I need them inside almost every controllers to check the user identity and get only the user's right record on the db?

The Controller Action:

[Authorize]
public class PortafoglioController : Controller
{
    private readonly FundMonitorDb _db = new FundMonitorDb();

    //
    // GET: /Portafoglio/

    public ActionResult Index()
    {
        string userName = null;

        if (HttpContext.User.Identity.IsAuthenticated)
        {
            userName = HttpContext.User.Identity.Name;
        }

        var result = from p in _db.Portafogli
                     join u in _db.UserProfiles on p.UserId equals u.UserId
                     where u.UserName.Equals(userName)
                     select new PortafoglioIndexViewModel
                         {
                             Id = p.Id,
                             DataCreazione = p.DataCreazione,
                             Etichetta = p.Etichetta,
                             NumeroFondi = p.Fondi.Count(),
                             Username = userName
                         };

        return View(result);
    }
}

The View Model:

public class PortafoglioIndexViewModel
{
    public int Id { get; set; }

    [Display(Name = "Etichetta")]
    public string Etichetta { get; set; }

    [Display(Name = "Creato il"), DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
    public DateTime DataCreazione { get; set; }

    [Display(Name = "N. fondi")]
    public int NumeroFondi { get; set; }

    public string Username { get; set; }
}

The View:

@model FundMonitor.Web.ViewModels.PortafoglioIndexViewModel

@{
    ViewBag.Title = "Portafogli di " + Model.Username;
}

<h2>I miei portafogli</h2>

<p>
    @Html.ActionLink("Crea nuovo", "Create")
</p>
<table>
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Etichetta)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.DataCreazione)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.NumeroFondi)
        </th>
        <th></th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Etichetta)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.DataCreazione)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.NumeroFondi)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
            @Html.ActionLink("Details", "Details", new { id=item.Id }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.Id })
        </td>
    </tr>
}

</table>
Community
  • 1
  • 1
Cheshire Cat
  • 1,941
  • 6
  • 36
  • 69

1 Answers1

0

One approach to this could be to create a ViewModel that contains the user details and an IEnumerable of PortafoglioIndexViewModels like this:

Step 1: Create the new wrapper, WrapperViewModel (as an example name):

public class WrapperViewModel
{
    public string Username { get; set; }
    public IEnumerable<PortafoglioIndexViewModel> Children { get; set; }

    public WrapperViewModel()
    {

    }

    public WrapperViewModel(string userName, IEnumerable<PortafoglioIndexViewModel> children)
    {
        Username = userName;
        Children = children;
    }

}

Step 2: In the controller, populate the WrapperViewModel with your results and pass to the view. So replace the return View(result); to:

return View(new WrapperViewModel(userName, result));

Step 3: The view needs to be changed to accommodate the new WrapperViewModel:

@model FundMonitor.Web.ViewModels.WrapperViewModel

@{
    ViewBag.Title = "Portafogli di " + Model.Username;
}

<h2>I miei portafogli</h2>

<p>
    @Html.ActionLink("Crea nuovo", "Create")
</p>

@if (Model.Children != null)
{
    <table>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Children.First().Etichetta)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Children.First().DataCreazione)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Children.First().NumeroFondi)
            </th>
            <th></th>
        </tr>

        @foreach (var item in Model.Children)
        {
            <tr>
                <td>
                    @Html.DisplayFor(x => item.Etichetta)
                </td>
                <td>
                    @Html.DisplayFor(x => item.DataCreazione)
                </td>
                <td>
                    @Html.DisplayFor(x => item.NumeroFondi)
                </td>
                <td>
                    @Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
                    @Html.ActionLink("Details", "Details", new { id = item.Id }) |
                    @Html.ActionLink("Delete", "Delete", new { id = item.Id })
                </td>
            </tr>
        }

    </table>
}

Hope this gives you some direction...

Alistair Findlay
  • 1,154
  • 6
  • 12
  • It should work but it seems a little bit clumsy solution. Just because I'll have many view models and so, creating a new view model wrapper for everyone of them will quickly grow up the amount of classes and also will make more difficult code maintenance. I'd prefer a "best practice" solution, since I want to learn the MVC pattern. – Cheshire Cat Mar 20 '13 at 15:24
  • This is essentially the MVC pattern - a ViewModel per View. Code maintenance shouldn't be too difficult if you structure your models and view models correctly! Here's another thread that addresses your concern: http://stackoverflow.com/questions/5077393/asp-net-mvc-should-you-create-a-model-for-each-view (note the first comment from a guy with much more rep than me :)). – Alistair Findlay Mar 20 '13 at 15:46
  • Yes, indeed I want to make a View Model for each View. But you created a `WrapperViewModel` on my `PortafoglioIndexViewModel` that is already a View Model for the `Index` view of the `Portafoglio` model. – Cheshire Cat Mar 20 '13 at 17:00
  • One thing: I've missed out something that is perhaps causing the confusion - remove the `public string Username { get; set; }` from your viewmodel and the `Username = userName` from the `select new PortafoglioIndexViewModel` in the LINQ query, as these are redundant when using the new wrapper. Does that clear things up a bit? – Alistair Findlay Mar 20 '13 at 18:53
  • Not exactly... but maybe I'm missing something. Since my PortafoglioIndexViewModel is a ViewModel where I can have one or many "Portafoglio" (portfolios) associated to the same User, I don't really need the Username property for each portafoglio because it would be always the same, right? So I'll need something like: `public class PortafoglioIndexViewModel { public int Id { get; set; } public string Etichetta { get; set; } public DateTime DataCreazione { get; set; } public int NumeroFondi { get; set; } }` And so your Wrapper would take the Username property once. Right again? – Cheshire Cat Mar 25 '13 at 14:49
  • That's correct. Perhaps my use of the word "wrapper" is also misleading - I would have used a word more appropriate to your class names but my Italian isn't the best :). On a basic level, all ViewModels are essentially "wrappers" around data... – Alistair Findlay Mar 25 '13 at 19:26
  • OK. So now, my last question is: is there another way to do this, without using nested view models like you do? In that case the 1st is my `PortafoglioIndexViewModel` and the 2nd would be your `WrapperViewModel` that contains it. Can I have only my PortafoglioIndexViewModel for my Index view and use it like I had to do with the simple model class Portafoglio? So I can iterate through objects like standard scaffolded view does: `@Html.DisplayNameFor(model => model.Etichetta)`, `@Html.DisplayFor(modelItem => item.Etichetta)` and so on. Thanks for patience! I want to understand these concepts. – Cheshire Cat Mar 26 '13 at 09:41
  • Yes, you could just use the simple model class for `Portafoglio` instead of a dedicated ViewModel. However, one reason to create ViewModel's around models is to prevent against the "mass assignment vulnerability" which is a weakness of MVC: http://odetocode.com/blogs/scott/archive/2012/03/11/complete-guide-to-mass-assignment-in-asp-net-mvc.aspx - read all of it but especially the subheading, "An Architectural Approach". Perhaps because you're not accepting input you don't need to do this - but it's useful to know! – Alistair Findlay Mar 26 '13 at 17:50
  • Thanks. I already know Scott Allen and his blog. I also attended some video tutorial made by him. – Cheshire Cat Mar 28 '13 at 10:55