JohnB
JohnB

Reputation: 4359

How do I conditionally/dynamically include Razor subpages in an Asp.Net Core Razor page?

New to Asp.Net Core/Razor here.

I am building a Asp.Net Core Razor page. I have a main Razor page which serves as a parent page. I have a collection of sub pages (over 20 different children types).

I would like to be able to conditionally include a child page based on an identifier in my parent page's view model.

The basic simplified version of my parent Razor page would look something like this:

@model DocumentsViewModel

<div>
<!-- Some basic common form functionality--!>
</div>

<div class="WhereToEmbedChildForm">
        @Html.Partial("SomeChildViewName")

        @{ Html.RenderPartial("SomeChildViewName"); }

        @{<partial name="SomeChildViewName" for="CurrentDocumentTypeViewModel"/>}
</div>

My parent page view model would look something like this:

public class SampleModel
{
   public int ChildViewType { get; set; }
   public string ChildViewModelName { get; set; } 
}

To accomplish this, I have some questions:

  1. Is it possible to dynamically create my ViewModel object (DynamicObject) to include the child's view model?

  2. How would I dynamically include the child view (and associated view model) in my parent page?

  3. Should I be using or @Html.Partial()/@{Html.RenderPartial} ?

  4. Given Razor, is it possible to do what I'm attempting?

Upvotes: 1

Views: 1585

Answers (1)

Roar S.
Roar S.

Reputation: 11124

Please note that using tag library is preferred above HTML helpers when creating new apps.

A proof-of-concept:

Updated version of SampleModel:

public class SampleModel
{
    public object ChildViewModel { get; set; } 
    public string ChildViewName { get; set; }
}

And a simple child model

public class ModelForPartialOne
{
    public string SomeDemoProperty { get; set; }
}

In controller

private ModelForPartialOne GetModelForPartialOneWithDataFromDb()
{
    // just a mock db call
    return new ModelForPartialOne {SomeDemoProperty = "Hello from one"};
}

public IActionResult SomeControllerAction()
{
    const int childViewType = 42; // you will get this from somewhere
    
    switch (childViewType)
    {
        case 42:
            return View(new SampleModel
            {
                ChildViewModel = GetModelForPartialOneWithDataFromDb(),
                ChildViewName = "_partialOne",
            });
    }
}

In main Razor view

@model SampleModel

<div class="WhereToEmbedChildForm">
    <partial name="@Model.ChildViewName" model="@Model.ChildViewModel"/>
</div>

_partialOne.cshtml

@model ModelForPartialOne

<h1>Hello from partial one</h1>
<p>SomeDemoProperty: @Model.SomeDemoProperty</p>

In order to avoid a bloated controller, logic for populating view models should be delegated to business logic services.

Alternative to the above solution, is to use view components.

Code is tested using a Core 5 MVC app.

Update

OP asked me this later on:

However, now I'm attempting to retrieve data from my partial view on a PostBack. The submit button lives on my parent page.

Let's rewrite the main view like this:

@model SampleModel

<div class="WhereToEmbedChildForm">
    <form method="post" asp-action="@Model.PostbackAction">
        <partial name="@Model.ChildViewName" model="@Model.ChildViewModel"/>
        <button type="submit">Click Me</button>
    </form>
</div>

We'll need a partial _partialTwo.cshtml with form data like this:

@model ModelForPartialOne

Some demo property:
<input asp-for="SomeDemoProperty">

Add this property to SampleModel: public string PostbackAction { get; set; }

And modify controller GET method to return this:

return View(new SampleModel
{
    ChildViewModel = GetModelForPartialOneWithDataFromDb(),
    ChildViewName = "_partialTwo",
    PostbackAction = "MyPostbackAction"
});

With the above changes, we're able to open a page with a form. Now we'll need a controller action to handle the postback. This is where the fun stops, because we cannot use object as type for model parameter in POST method; the code needs to know the type to use for model binding.

[HttpPost]
public IActionResult MyPostbackAction(ModelForPartialOne model)
{
    // do something
    return View("Index");
}

Upvotes: 2

Related Questions