0

I have a menu and a content area which displays content based on the selected item in the menu. Since the user is allowed to change the structure of the main menu, I decided that everything will be on page /home/index and will have a guid assigned to the content which needs to be shown. I started with the idea to introduce partial views, but realized that ASP.NET Core doesn't have RenderAction anymore and was replaced by ViewComponents. So I used ViewComponents and everything works fine, except that I've stumbled on a situation where I need to have a component as a submit form.

In an example: One menu item is the menu is a component that shows a list of users. Another menu item is a component that creates a new user. On create user component I'll have a form that needs to be filled and on successful submit, I want to redirect the user to the component that shows a list of users. In the case of unsuccessful submit, error, wrong input I would of course not want to redirect the user to the list of users.

Since ViewComponents' job is to display view, how should I approach this issue? I'm looking for pointers in the right direction. I have little experience in this field so any help would be appreciated.

UPDATE

In Index.cshtml:

<tbody>
    @foreach (string item in Model.Components)
    {
        <tr>
            <td>
                <div class="col-md-3">
                    @await Component.InvokeAsync(@item)
                </div>
            </td>
        </tr>
    }
</tbody>

This is inside the content area. Components are string names of components I'd like to show in the content area (currently listed one after the other).

My ViewComponent which will get called when I click on the menu item to display the form:

public class TestFormViewComponent : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync()
    {
        return View("_TestForm", new TestModelPost());
    }
}

My _TestForm.cshtml component:

@model TestModelPost
<form asp-controller="Home" asp-action="TestPost" method="post">
    <label asp-for="Name"></label>
    <input asp-for="Name" /><br />
    <label asp-for="Surname"></label>
    <input asp-for="Surname" /><br />
    <button type="submit">Go</button>
</form>

And the action TestPost called:

[HttpPost]
public IActionResult TestPost(TestModelPost model)
{
    // Save model data, etc
    // !ModelState.IsValid -> go back to form

    // Success -> go to specific id
    return RedirectToAction("Index", new { id = 1 });
}

How should I approach this? Or rather, am I even on the right track? I'm not sure how I would go "back" to that view I created in my _TestForm component in case the input was incorrect.

2 Answers 2

4

View components, just like child actions before them in ASP.NET MVC do not support POSTs. You need a full fledged action to handle the form post, which will need to return a full view. Essentially, you just need to think about it more abstractly.

A view component is just ultimately a means of dumping some HTML into the eventual response. When the full response is returned to the client, there's no concept of what was a view component, a partial view, etc. It's just an HTML document. Part of that HTML document is your form. That form will have an action, which ultimately should be a route that's handled by one of your controller actions. A traditional form post cause the entire browser view to change, so the response from this action should either be a full view or a redirect to an action that returns a full view. The redirect is the more appropriate path, following the PRG pattern. It is then the responsibility of that view that is eventually returned to determine how the content is constructed, using view components, partials, etc. as appropriate.

Long and short, this really has nothing to do with your view component at all. All it is doing is just dropping the form code on the page.

For forms like this that are part of the layout, it's usually best to include a hidden input that contains a "return URL" in the form. The action that handles the form, then can redirect back to this URL after doing what it needs to do, in order to give the semblance that the user has stayed in the same place.

Sign up to request clarification or add additional context in comments.

1 Comment

First off love your movies ;), secondly this sort of kills the idea of a "component" in that it's now no longer self contained and one would need to fire off an action on the containing view controller of the component and now that's responsible for running code for the post vs the component itself which kind of sucks. I hear often VC's replace RenderAction() however in this case it doesn't seem to do that as RenderAction() can handle posts/gets where VC's can't. So how does one actually replace that functionality in MVC Core? I get this is old but still an issue it seems.
0

Kindly refer to the following link:

https://andrewlock.net/an-introduction-to-viewcomponents-a-login-status-view-component/

it might be of help

public class LoginStatusViewComponent : ViewComponent
{
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly UserManager<ApplicationUser> _userManager;

    public LoginStatusViewComponent(SignInManager<ApplicationUser> signInManager, UserManager<ApplicationUser> userManager)
    {
        _signInManager = signInManager;
        _userManager = userManager;
    }

    public async Task<IViewComponentResult> InvokeAsync()
    {
        if (_signInManager.IsSignedIn(HttpContext.User))
        {
            var user = await _userManager.GetUserAsync(HttpContext.User);
            return View("LoggedIn", user);
        }
        else
        {
            return View();
        }
    }
}

Our InvokeAsync method is pretty self explanatory. We are checking if the current user is signed in using the SignInManager<>, and if they are we fetch the associated ApplicationUser from the UserManager<>. Finally we call the helper View method, passing in a template to render and the model user. If the user is not signed in, we call the helper View without a template argument.

As per your code segment you can try to modify it as follows:

public class TestFormViewComponent : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync()
    {
      if (_signInManager.IsSignedIn(HttpContext.User))
        {
       //user list display
        return View("_TestForm", new TestModelPost());
        }
      else
        {
          return View();
        }
    }
}

1 Comment

Hello, I updated my first post with the code I wrote. Any further pointers?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.