Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • nickornotto 397 posts 900 karma points
    Aug 08, 2014 @ 13:29
    nickornotto
    0

    UmbracoTemplatePage and strongly typed view not working together

    How to list custom data on the page and put it in the shape of umbraco template?

    I use both strongly typed view and umbraco template in one view but I get errors as I couldn't use this combination.

    Here are details:

    I have a script importing excel data. My website is in umbraco 7. I use Import.cshtml view which calls upload.cshtml partial

    @inherits UmbracoTemplatePage
    @{
        Layout = "~/Views/Master.cshtml";
    
        var locations = new List<SelectListItem>
            {
            new SelectListItem {Selected = false, Text = "location 1", Value = ""},
            };
    
        var fileTypes = new List<SelectListItem>
            {
            new SelectListItem {Selected = false, Text = "type 1", Value = ""},
            };
    }
    
    <div id="page-bgtop">
        <div class="post">
            <h1>@Umbraco.Field("title")</h1>
            <div class="entry">
                @Html.Partial("Upload", new MvcImport.Models.ImportModel { FileTypes = fileTypes, Locations = locations })
            </div>
        </div>
    
        <div class="clear"> </div>
    </div>
    

    Upload.cshtml partial does the job in ImportController. The data are there inserted into nicely into

    List<CourseImport> courses = List<CourseImport>
    

    All I want to do with it now is to display them in a view or partial view. I tried several things:


    CASE 1

    1.

    in ImportController I do:

    return View("CoursesList", courses);
    

    2.

    The view includes a table which displays the rows and starts with

    @inherits UmbracoTemplatePage
    @model IEnumerable<MvcImport.Models.CourseImport>
    @{
        Layout = "~/Views/Master.cshtml";
    }
    

    3.

    In this case I get:

    Parser Error Message: The 'inherits' keyword is not allowed when a 'model' keyword is used.
    

    CASE 2

    1.

    As 1. in Case 1.

    2.

    The view starts with (so without @inherits UmbracoTemplatePage)

    @model IEnumerable<MvcImport.Models.CourseImport>
    @{
        Layout = "~/Views/Master.cshtml";
    }
    

    3.

    I get:

    The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[MvcImport.Models.CourseImport]', but this dictionary requires a model item of type 'Umbraco.Web.Models.RenderModel'.
    

    CASE 3

    1.

    In ImportController I do

    return PartialView("CoursesList", courses);
    

    2.

    I'm now using partial view which it similar to the view in the above points and also is strongly typed eg.

    @model IEnumerable<MvcImport.Models.CourseImport>
    

    3.

    In this case the data is displayed but without my umbraco template shape, no styling etc.

    If in the partial view I include

    @{
        Layout = "~/Views/Master.cshtml";
    }
    

    I get the same error as in Case 2


    Can someone advise me how to do it? I guess there must be a solution but I am very new to MVC so I don't get how things work yet too much.

    Thanks.

  • Paul 10 posts 102 karma points
    Aug 08, 2014 @ 22:36
    Paul
    1

    Hi Eva,

    There are a few ways to achieve what you are trying to do but I think the simplest way for you would be to stick with Case 3 where you return a partial from the controller but rather than having the model as IEnumerable<MvcImport.Models.CourseImport> you can use the following:

    @inherits Umbraco.Web.Mvc.UmbracoViewPage<IEnumerable<MvcImport.Models.CourseImport>>
    

    This should give you access to the Umbraco helper in the partial and allow you to access page properties in the same way you normally would in a view.

    You don't say how you are calling the action in the ImportController so you might need to a make a few more changes to the code in the View to get this working how you want. There's a bit more reading on the topic here which might help a bi: http://our.umbraco.org/documentation/Reference/Mvc/partial-views#StronglytypedPartialViews

    Hope this get you a bit closer to solving the problem

  • nickornotto 397 posts 900 karma points
    Aug 11, 2014 @ 12:14
    nickornotto
    0

    Thanks Paul,

    I have tried that but I'm getting

    foreach statement cannot operate on variables of type 'Umbraco.Web.Mvc.UmbracoViewPage<System.Collections.Generic.IEnumerable<MvcImport.Models.CourseImport>>' because 'Umbraco.Web.Mvc.UmbracoViewPage<System.Collections.Generic.IEnumerable<MvcImport.Models.CourseImport>>' does not contain a public definition for 'GetEnumerator'
    

    This is my ImportController:

    namespace MvcImport.Controllers
    {
    public class ImportController : SurfaceController
    {
        public ActionResult Import()
        {
            return View(new ImportModel());
        }
    
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult ImportExcel(ImportModel model)
        {
            if (model.FileUpload != null && model.FileUpload.ContentLength > 0)
            {
                try
                {
                    string uploadDir = MvcImport.Settings.UploadPath;
                    string origFileName = model.FileUpload.FileName;
                    string extension = origFileName.Substring(origFileName.LastIndexOf('.') + 1);
                    string pathToCheck = Path.Combine(Server.MapPath(uploadDir), origFileName);
    
                    // Check to see if a file already exists with the
                    // same name as the file to upload.
                    if (!System.IO.File.Exists(pathToCheck))
                    {
                        string companyId = MvcImport.Member.CompanyIdDummy;
                        string userId = MvcImport.Member.UserIdDummy;
    
                        string tempfileName = companyId.ToLower() + "-temp." + extension;
                        pathToCheck = Path.Combine(Server.MapPath(uploadDir), tempfileName);
                        model.FileUpload.SaveAs(pathToCheck);
    
                        var importFile = new ImportFile
                        {
                            CompanyId = companyId,
                            MemberId = userId,
                            Filename = tempfileName,
                            FilenameOriginal = origFileName,
                            FileType = extension,
                            AddressId = model.Location,
                            DateCreated = DateTime.UtcNow,
                            DeleteExisting = model.DeleteExisting
                        };
    
                        //db.Insert(importFile);
    
                        CreateImportNode(importFile);
    
                        ExcelObject excel = new ExcelObject(pathToCheck);
                        DataTable dt = excel.GetSchema();
                        string sheetName = dt.Rows[0]["TABLE_NAME"].ToString();
                        DataTable courseTable = excel.ReadTable(sheetName);
                        excel.Connection.Close();
    
                        if (courseTable != null)
                        {
                            List<CourseImport> courses = new List<CourseImport>();
                            int dataRowNumber = 0;
                            foreach (DataRow row in courseTable.Rows)
                            {
                                ViewData["categories"] = ConvertCategoryToList();
    
                                var course = new CourseImport
                                {
                                    CompanyId = MvcImport.Member.CompanyIdDummy,
                                    UserId = MvcImport.Member.UserIdDummy,
                                    CategoryId = Convert.ToInt32(row["kategoria id"]),
                                    TypeId = Convert.ToInt32(row["rodzaj id"]),
                                    Modes = row["tryby"].ToString(),
                                    AddressId = Convert.ToInt32(row["adres id"]),
                                    Ref = row["Ref"].ToString(),
                                    Title = row["tytul"].ToString(),
                                    Tags = row["tagi"].ToString(),
                                    Description = row["program"].ToString(),
                                    Level = row["poziom"].ToString(),
                                    Students = row["uczestnicy"].ToString(),
                                    AdmissionInfo = row["warunki przyjecia"].ToString(),
                                    Diploma = row["dyplom"].ToString(),
                                    AdmissionFee = Convert.ToSingle(row["wpisowe"]),
                                    Fee = Convert.ToSingle(row["koszt"]),
                                    FeeNet = Convert.ToSingle(row["netto"]),
                                    FeeForID = Convert.ToInt32(row["koszt za"]),
                                    FeeOther = row["koszt inne"].ToString(),
                                    Other = row["inne informacje"].ToString(),
                                    PayOn = Convert.ToBoolean(row["plac z edu"]),
                                    Discount = Convert.ToInt32(row["rabat"]),
                                    Status = Convert.ToInt32(row["status"])
                                };
                                courses.Add(course);
    
                                dataRowNumber++;
                            }
                            return PartialView("CoursesList", courses);
                        }
                    }
    
                    TempData.Add("Success", true);
                }
                catch (Exception ex)
                {
                    return CurrentUmbracoPage();
                }
            }
    
            //redirect to current page to clear the form
            return RedirectToCurrentUmbracoPage();
        }
    }
    }
    

    and my partial view:

    @model UmbracoViewPage < IEnumerable < MvcImport.Models.CourseImport >>
    
    <table class="list">
    @foreach (var item in Model)
    {
        SelectList categories1 = new SelectList((IEnumerable<MvcImport.Models.Category>)ViewData["categories"], "Id", "Name", item.CategoryId);
        var categories = (IEnumerable<MvcImport.Models.Category>)ViewData["categories"];
        <tr>
            <td>
                <h2>@Html.DisplayFor(modelItem => item.Title)</h2>
            </td>
        </tr>
        <tr>
            <td>@Html.DisplayFor(modelItem => categories.First(m => m.Id == item.CategoryId).Name)
            @Html.DropDownListFor(modelItem => item.CategoryId, categories1, "Select an item")</td>
        </tr>
        <tr>
            <td>
                <label>@Html.DisplayNameFor(model => model.Ref)</label>
                @Html.DisplayFor(modelItem => item.Ref)
            </td>
        </tr>
    }
    
    </table>
    
  • Paul 10 posts 102 karma points
    Aug 11, 2014 @ 22:18
    Paul
    100

    Hi Eva,

    Thanks for the extra information, its a bit clearer how you have everything hooked up now and I don't think you are too far away from solving the issue. There is a bit more work to do in order to set this up so I'll go over those first. To start with you are going to need a new viewmodel class which will look like this

    public class CourseImportViewModel : RenderModel {
        public CourseImportViewModel() : this(new UmbracoHelper(UmbracoContext.Current).TypedContent(UmbracoContext.Current.PageId)) { }
        public CourseImportViewModel(IPublishedContent content, CultureInfo culture) : base(content, culture) { }
        public CourseImportViewModel(IPublishedContent content) : base(content) { }
        public IEnumerable<CourseImport> Courses { get; set; }
    }
    

    The key part of this is that it inherits from the RenderModel class, this means it can contain all the Umbraco page data and can be passed into a partial view while allowing the partial to have a layout. The three constructors allow you to set the content while the IEnumerable<CourseImport> Courses property will contain your data and will be populated in your controller in the same way it currently is.

    Your controller will return the partial in the same way as it does now but will now pass the view model and not just a list of your courses.

    return PartialView("CoursesList", new CourseImportViewModel { Courses = courses });
    

    Your partial will have to be slightly modified to accept this new view model, notice it uses inherits and not model and now has to use Model.Courses in the for loop

    @inherits Umbraco.Web.Mvc.UmbracoViewPage<MvcImport.Models.CourseImportViewModel>
    @{
        Layout = "~/Views/Master.cshtml";
    }    
    
        <table class="list">
        @foreach (var item in Model.Courses)
        {
            SelectList categories1 = new SelectList((IEnumerable<MvcImport.Models.Category>)ViewData["categories"], "Id", "Name", item.CategoryId);
            var categories = (IEnumerable<MvcImport.Models.Category>)ViewData["categories"];
            <tr>
                <td>
                    <h2>@Html.DisplayFor(modelItem => item.Title)</h2>
                </td>
            </tr>
            <tr>
                <td>@Html.DisplayFor(modelItem => categories.First(m => m.Id == item.CategoryId).Name)
                @Html.DropDownListFor(modelItem => item.CategoryId, categories1, "Select an item")</td>
            </tr>
            <tr>
                <td>
                    <label>@Html.DisplayNameFor(model => model.Ref)</label>
                    @Html.DisplayFor(modelItem => item.Ref)
                </td>
            </tr>
        }
    
        </table>
    
  • nickornotto 397 posts 900 karma points
    Aug 12, 2014 @ 13:42
    nickornotto
    0

    Fabulous, thanks Paul. It works nicely now.

    However I have trouble to get courses back from a session variable I'm setting at the end of my import.

    So in the ImportController I added

    Session["COURSESIMPORT"] = courses;
    

    Now I replaced Import() function with:

    public ActionResult Import()
    {
        ViewData["categories"] = ConvertCategoryToList();
    
        if (Session["COURSESIMPORT"] != null)
        {
            List<MvcImport.Models.CourseImport> courses = (List<MvcImport.Models.CourseImport>)Session["COURSESIMPORT"];
            return PartialView("CoursesList", new CourseImportViewModel { Courses = courses });
        }
        else
        {
            return View(new ImportModel());
        }
    }
    

    but my view does not seem to see this at all. So it returns a standard page with form included in the view but it doesn't see anything in

    ActionResult Import()
    

    Not sure I'm calling this bit properly.

  • Paul 10 posts 102 karma points
    Aug 12, 2014 @ 20:15
    Paul
    0

    Hi Eva,

    Can you put a break point on the Import action to see what is in the session variable at that point and also double check the code is being hit?

    Are you calling this action directly from the template in Umbraco using Html.Action?

  • nickornotto 397 posts 900 karma points
    Aug 13, 2014 @ 11:36
    nickornotto
    0

    That's the case - I don't think it sees the controller at all. The debugger doesn't stop at the breakpoints.

    Also when I put simply

    ViewBag.Message = "test";
    

    in the

    ActionResult Import()
    

    and put it in the Import.cshtml view - nothing happens (ViewBag.Message value doesn't get displayed)

    I'm not using Html.Action

    I want the controller to perform ActionResult Import() on the page load eg. if the session is not present or empty I get upload form displayed (I pasted Import.cshtml code at the begining of my post) - the bit in

    @Html.Partial("Upload", new MvcImport.Models.ImportModel { FileTypes = fileTypes, Locations = locations })
    

    If it has value it displays the course list as per our previous discussion.

    Instead I get Upload form displayed each time and ActionResult Import() seems to be ignored (eg. ViewBag.Error).

    Am I not doing it correctly?

  • Paul 10 posts 102 karma points
    Aug 13, 2014 @ 21:44
    Paul
    0

    I think there is a little bit of a mix up between pure MVC and the default Umbraco way of working with templates, its a little bit different so its easy to get caught out. The best way forward is to replace this line in your Import.cshtml file

    @Html.Partial("Upload", new MvcImport.Models.ImportModel { FileTypes = fileTypes, Locations = locations })
    

    With this

    @Html.Action("Import", "Import")
    

    This will call the action direct from the view which is what you want in this case. You will also need to adjust your controller slightly to make it return the correct partial view, we wont be returning a full view from the action as you originally had it set up. This would work in pure MVC but not in this case where you are on an Umbraco page.

    [ChildActionOnly]
    public ActionResult Import()
    {
        ViewData["categories"] = ConvertCategoryToList();
    
        if (Session["COURSESIMPORT"] != null)
        {
            List<MvcImport.Models.CourseImport> courses = (List<MvcImport.Models.CourseImport>)Session["COURSESIMPORT"];
            return PartialView("CoursesList", new CourseImportViewModel { Courses = courses });
        }
        else
        {
            var locations = new List<SelectListItem>
            {
                new SelectListItem {Selected = false, Text = "location 1", Value = ""},
            };
    
            var fileTypes = new List<SelectListItem>
            {
                new SelectListItem {Selected = false, Text = "type 1", Value = ""},
            };
    
            return Partial("Upload", new MvcImport.Models.ImportModel { FileTypes = fileTypes, Locations = locations })
        }
    }
    

    The attribute [ChildActionOnly] means this action can not be accessed via a url such as domain.com/Import which is what you want in this case as you are accessing it via an Umbraco page. This also means you can take most of the cs code out of your view file as its now in the controller.

  • nickornotto 397 posts 900 karma points
    Aug 15, 2014 @ 11:03
    nickornotto
    0

    Working almost great, thanks Paul!

    The values uploaded from excel now display well and also I can now load the courses from the session on the new page load.

    The only small problem now is on new page load.

    The CourseList partial view includes

    @{
        Layout = "~/Views/Master.cshtml";
    }
    

    so my list of data is shaped in the template on calling partial view after posting:

    return PartialView("CoursesList", new CourseImportViewModel { Courses = courses });
    

    However when I access page without posting eg. when the first condition of Import() action happens

    if (Session["COURSESIMPORT"] != null)
    

    the page content is inside the master template which is nested in the same master template again. If I remove

    @{
        Layout = "~/Views/Master.cshtml";
    }
    

    from the view, the content is not wrapped in the template when displaying the data after posting.

  • Paul 10 posts 102 karma points
    Aug 15, 2014 @ 11:13
    Paul
    0

    Did you try removing the Layout = "~/Views/Master.cshtml"; from the Upload partial view? Since you are now calling the action from the View it will simply write out the result of the action at the location in the View where you put @Html.Action

  • nickornotto 397 posts 900 karma points
    Aug 15, 2014 @ 12:40
    nickornotto
    0

    If I do that it fails on line

    @Html.Action("Index", "Import") 
    

    saying

    System.InvalidOperationException: The model item passed into the dictionary is of type 'MvcImport.Models.ImportModel', but this dictionary requires a model item of type 'Umbraco.Web.Models.RenderModel'.
    
  • Paul 10 posts 102 karma points
    Aug 15, 2014 @ 14:05
    Paul
    0

    You should be able to make the ImportModel inherit from the Render model, the same as you do for the CourseImportViewModel is should look something like:

    public class ImportModel: RenderModel {
        public ImportModel() : this(new UmbracoHelper(UmbracoContext.Current).TypedContent(UmbracoContext.Current.PageId)) { }
        public ImportModel(IPublishedContent content, CultureInfo culture) : base(content, culture) { }
        public ImportModel(IPublishedContent content) : base(content) { }
        //the other properties you need to pass to the view
    }
    
  • nickornotto 397 posts 900 karma points
    Aug 22, 2014 @ 13:25
    nickornotto
    0

    Paul, thank you! I would never resolve most things on my own.

    I still need to learn a lot but that was a big step.

Please Sign in or register to post replies

Write your reply to:

Draft