7

I have a couple follow-up questions for this question: Sitecore MVC - Accessing the Data Source of a Control

  1. In the foreach loop, how do I output an image?

  2. Since the model for the view is now strongly typed, how can I get the RenderingModel fields using the Html helper syntax?

    @Html.Sitecore().Field("Title")

In my controller I have:

public ActionResult Index()
{
    var dataSourceId = RenderingContext.CurrentOrNull.Rendering.DataSource;
    var dataSource = Sitecore.Context.Database.GetItem(dataSourceId);
    var viewModel = new RotatorViewModel();
    viewModel.RotatorItems = dataSource.Children;
    viewModel.Initialize(RenderingContext.Current.Rendering);
    return View(viewModel);
}

I am trying to inherit RenderingModel:

public class RotatorViewModel : RenderingModel
{
    public IEnumerable<Item> RotatorItems { get; set; }

    public HtmlString Title
    {
        get
        {               
            return new HtmlString(FieldRenderer.Render(this.Item, "Title"));
        }
    }

    public HtmlString Text
    {
        get
        {
            return new HtmlString(FieldRenderer.Render(this.Item, "Text"));
        }
    }
}

In my view I have:

@using Sitecore.Mvc.Presentation
@using Sitecore.Mvc
@model BentleyBootstrap.Controllers.HomeController.RotatorViewModel

@foreach (var item in Model.RotatorItems)
{
    <h2>@item.Fields["Title"]</h2>
    @item.Fields["Sub Title"]
}
@Model.Title
<br />
@Model.Text
  • 2
    The accepted answer on the question you link to uses a controller rendering. I would not recommend combining that approach with the IRenderingModel approach, which is geared more toward view renderings. If you have a controller, that is where you should be populating your model. – Ben Golden Oct 5 '16 at 20:55
  • What about the RenderingModel itself? Is that also not recommended? – ADH Oct 5 '16 at 20:57
  • @RichardSeal Can you recommend anything for me to read? I have been trying these videos: sitecore-community.github.io/docs/sitecore-mvc – ADH Oct 5 '16 at 20:59
7

As has been mentioned. You should do this in your Constructor action and view model.

This is an example of how you could do this without and ORM. But really if you are not using an ORM you are making life more difficult for yourself. Have a look at Fortis (my preferred choice) or Glass Mapper (another very good choice).

Create your view model properly:

public class RotatorItem
{
    public RotatorItem(Item item)
    {
        this.Title = new HtmlString(FieldRenderer.Render("Title", item));
        this.ItemImage = new HtmlString(FieldRenderer.Render("Item Image", item));
    }

    public IHtmlString Title { get; }

    public IHtmlString Text { get; }

    // You can add more fields here

    public IHtmlString ItemImage { get; }
}

public class RotatorViewModel 
{
    public RotatorViewModel() 
    {
        this.Items = new List<RotatorItem>();
    }

    public IHtmlString Title { get; set; }

    public IList<RotatorItem> Items { get; set;}
}

Now we can populate that in the action:

public ActionResult Index()
{
    var dataSourceId = RenderingContext.CurrentOrNull.Rendering.DataSource;
    var dataSource = Sitecore.Context.Database.GetItem(dataSourceId);

    var viewModel = new RotatorViewModel
    {
        Title = new HtmlString(FieldRenderer.Render("Title", dataSource)),
        Text = new HtmlString(FieldRenderer.Render("Text", dataSource))
    }

    foreach(var child in dataSource.Children)
    {
        viewModel.Items.Add(
            new RotatorItem(child);
        )
    }

    return View(viewModel);
}

Notice that the view model is just a straight POCO - no need to inherit any other rendering model here.

Now your razor becomes:

@using Sitecore.Mvc.Presentation
@using Sitecore.Mvc
@model RotatorViewModel

@foreach (var item in Model.RotatorItems)
{
    <h2>@item.Title</h2>
    @item.ItemImage
    <!-- etc... add other fields here -->
}

@Model.Title
<br />
@Model.Text

The only real "Sitecore" bit of this is populating the view model properties with rendered Sitecore fields - the rest is really just basic MVC stuff. Don't try to over think the MVC stuff and let Sitecore get in the way of doing it simply.

And don't do any of the above - use an ORM!

| improve this answer | |
7

I'm not sure what code you have currently but in your controller you could do something like this to get the image urls for the sub items and return them in a view model:

  List<string> imageUrls;
  foreach(var item in parent.GetItems())
  {
  string hashedUrl = HashingUtils.ProtectAssetUrl(Sitecore.StringUtil.EnsurePrefix('/', Sitecore.Resources.Media.MediaManager.GetMediaUrl(item));

  imageUrls.Add(hashedUrl);
  }

  var pageViewModel = new PageViewModel {ImageUrls = imageUrls};

Then in your view you can do something like this:

@foreach (var imageUrl in PageViewModel.ImageIDs) {
<div class="image-container">
    <img src="@imageUrl">
</div>

Also have you looked at using Glass (http://www.glass.lu/mapper/sc)? this really makes testing and creating Sitecore views and controllers a lot easier.

| improve this answer | |
  • The code is in the answer for the referenced question. I will take a look at GlassMapper. – ADH Oct 5 '16 at 20:37
1

If you look at the Sitecore Ignition (https://github.com/sitecoreignition/SitecoreIgnition) code you can see a number of examples of this being done. We use Glass Mapper as has been mentioned before, but there are others as well. You can look at code specifically in Ignition.Sc under Components. We use Controller Renderings with Glass and strongly typed models and there are a number of examples there.

One example is our Content Block

@model Ignition.Sc.Components.Content.IContentBlock

<div class="col-footer col-md-4 col-xs-6">
    <h3>@Model.Heading</h3>
    <p class="contact-us-details">
    @Html.Raw(Model.Copy1)
    </p>
</div>

In this case we use automatic data binding to populate the view model, but you could just as easily map your DTO's to your ViewModels manually. Using Glass, the Datasource property will be set for you assuming the type has been defined in the GlassCache.

http://glass.lu/ has examples and tutorials as well in addition to the examples in Ignition.

| improve this answer | |
1

I am building off of the accepted answer for the question you linked to.

View Model

public class RotatorViewModel
{
    public IEnumerable<Slide> Slides { get; set; }
    public HtmlString Title { get; set; }
    public HtmlString Text { get; set; }

    public class Slide
    {
        public HtmlString Title { get; set; }
        public HtmlString SubTitle { get; set; }
        public HtmlString Image { get; set; }
    }
}

Controller

public class RotatorController : SitecoreController 
{
    public ActionResult Index()
    {
        var dataSource = RenderingContext.Current.Rendering.Item;
        var viewModel = new RotatorViewModel
        {
            Title = FieldRenderer.Render(datasource, "Title"),
            Text = FeildRenderer.Render(datasource, "Text")
        };
        viewModel.Slides = datasource.Children
            .Select(c => new RotatorViewModel.Slide
                {
                    Title = FieldRenderer.Render(c, "Title"),
                    SubTitle = FieldRenderer.Render(c, "Sub Title"),
                    Image = FieldRenderer.Render(c, "Image")
                });

        return View(viewModel);
    }
}

View

@model RotatorViewModel 

@foreach (var slide in Model.Slides)
{
    <h2>@slide.Title</h2>
    @slide.SubTitle
    @slide.Image
}
@Model.Title
<br />
@Model.Text

As others have mentioned, it would be better to use a mapper/wrapper library like Glass Mapper or Synthesis so you don't have to have the magic strings in there. In fact, if you were using one of those libraries, your controller would hardly be needed for this simple case.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.