How to create dynamic, multiple partial views usin

2019-04-09 02:03发布

问题:

I am trying to have a general home page that depending on the parameter passed to the control, different content (modules) will be displayed.

For example, a user may select Kentucky from the menu and the id for Kentucky is 1. The home controller gets the id (1) and determines the possible modules for that state (a simple db call.) Perhaps there is an announcements module and a contacts module for the state. An announcements module could have several items but it's only one module. There would be a partial view for each type of module.

Here is the basic setup I have.

public interface IModuleRepository
{
    IList<MenuItemModule> GetMenuItemModules(int menuItem);
    IList<Announcements> GetAnnouncements(int modID);
    IList<News> GetNews(int modID);
    IList<Contacts> GetContacts(int modID);
}



//business object
public class MenuItemModule
{

    private int _MenuItemID;
    private int _ModuleID;
    private int _ModuleDefID;
    private string _Src;
    private int _ModuleOrder;

//get, set properties for these...

}

//announcements entity
public class Announcements
{
    private int _ID = -1;
    private int _MenuItemID = -1;
    private int _ModuleID = -1;
    private string _Description = string.Empty;

//get set props ...
}

In my home controller...

public class HomeController : Controller
{


    private IModuleRepository modRepository;

    public HomeController(IModuleRepository modRepository)
    {
        this.modRepository = modRepository;
    }


    public ViewResult Item(string ItemID)
    {

        //returns a list of menuitemmodules for the page.  This gives me the Src or name of each
        //module on the page, i.e. Announcements, news, contacts, etc. 
        var modules = modRepository.GetMenuItemModules(Convert.ToInt32(ItemID)); 


        return View(modules);

   }

}

I have tried several different models to return but I always run up against some contstraint. If I pass the menuitemmodules to my Item.aspx, then I can do something like this:

   foreach (var mod in Model)           
   {               
        Html.RenderPartial(mod.Src, a);      //needs an announcement object though    
   } 

That makes it somewhat dynamic because I have the Src which would basically be something like "Announcements" and I can just create an announcements.ascx partial to process the module. But I have found it difficult to pass my menuitemmodule and an announcements entity as well.

I have also messed around with passing a more complex object and then testing every Src that comes through with an If statement. This would make scaling difficult in the future as I increase the number of possible modules in the app.

How can I solve my problem? I hope I have provided enough info. I like the basic idea here - http://www.mikesdotnetting.com/Article/105/ASP.NET-MVC-Partial-Views-and-Strongly-Typed-Custom-ViewModels but that seems to only work for static modules on a page.

I did try a composite view model called ModuleViewModel. Here is that attempt:

public class ModuleViewModel
{
    public IList<Announcements> announcements { get; set; }
    public IList<MenuItemModule> mods { get; set; }

}

If I pass that model to the Item.aspx I can do something like this (but I must be doing something wrong because something doesn't look right.)

   foreach (var mod in Model)           
   {

       if (mod.announcements.Count > 0)
       {
           Html.RenderPartial("Announcements", mod.announcements);
       }


   } 

Once again, scalability is going to haunt me. I would like to have something like this on item page:

   foreach (var mod in Model)           
   {

           Html.RenderPartial(mod.Src, mod);          

   } 

That would the correct partial view and pass it the correct model.

回答1:

Create Module classes that derive from a common Module base class:

public class AnnouncementsModule : Module
{
}

public class ContactsModule : Module
{
}

In controller:

Create your various modules and put them into your overall view module (here it has a property called Modules that is an array of Module:

var viewModel = new ComplexViewModel 
                { 
                    Modules = new [] 
                    { 
                       new ContactsModule(), 
                       new AnnouncementsModule() 
                    }
                 };
return View(viewModule);

In view:

@Html.DisplayFor(x => x.Modules);

Create the partial views for each Type of Module in the appropriate 'Shared` folder. (Run it without creating them and it will show you an exception with the locations where it's looking for them).



回答2:

After messing around with this for over a week, I finally managed to figure out how MVC can do what I want dynamically. I decided to post my solution for others that are new to MVC. Hopefully, the following will clear up the misunderstandings I had (although, at this point in my understanding of MVC, I cannot say this is the best approach.)

I will include the previous code snips and modifications for clarity:

 public interface IModuleRepository
 {
      IList<MenuItemModule> GetMenuItemModules(int menuItem);
      IList<Announcements> GetAnnouncements(int modID);
      IList<News> GetNews(int modID);
      IList<Contacts> GetContacts(int modID);
 }



//business object
public class MenuItemModule
{

    private int _MenuItemID;
    private int _ModuleID;
    private int _ModuleDefID;
    private string _Src;
    private int _ModuleOrder;

//get, set properties for these...

}

//announcements entity
public class Announcements  : MenuItemModule
{
    private int _ID = -1;
    private string _Description = string.Empty;

//get set props ...
}

I also added another class:

public class AnnouncementModule : MenuItemModule
{
    private IList<Announcements> _Announcements;
    //get set prop
}

...and I created a model for the view

public class HomeItemViewModel
{
    public MenuItemModule[] MenuItemModules { get; set; } //collection of menuitemmodules
}

In my home controller...

    var menuItemModules = modRepository.GetMenuItemModules(ItemID);

    if (menuItemModules.Count > 0)
    {
        AnnouncementModule aMod;
        MenuItemModule[] mods = new MenuItemModule[menuItemModules.Count()];

        int i = 0;

        //loop through each MenuItemModule assign to the appropriate model
        foreach (MenuItemModule mod in menuItemModules)
        {
            if (mod.Src == "Announcements")
            {
                aMod = new AnnouncementModule();
                aMod.Announcements = modRepository.GetAnnouncements(mod.ModuleID);

                //now add this to the menuitemmodule collection
                mods[i] = aMod;
            }

            if (mod.Src == "Contacts")
            {
                //...
            }

            i++;
        }

    }


    var viewModel = new HomeItemViewModel
    {
        MenuItemModules = mods
    };

    return View(viewModel);

Then I used the suggestion to use DisplayFor in the view. The view is strongly typed to HomeItemViewModel.

<%: Html.DisplayFor(m => m.MenuItemModules) %>

This iterates through the collection and based on the type, it will call that template. In this example, it calls AnnouncementModule.ascx which is strongly typed to AnnouncementModule.

foreach (var a in Model.Announcements)
{
    //a.Description will give us the description of the announcement
}

I realize there are slicker ways to code the controller, and I plan on refactoring, but this skeleton should provide the basics to solve the question I posted.