How to handle form post from View Component (Razor Core 2)
Asked Answered
B

3

6

This weekend a lot of struggle with a View Component.

I try to add a dropdownlist that does an auto postback onchange. This dropdownlist is on a view component.

I have 2 problems:

  1. I don't get the asp-page-handler after the post, does it work like I implemented it on the form-tag?
  2. Post calls method public void OnPost on razor page containing view component. I would think it would be better to have a method on the View Component like OnChangeProject?

The code of my View (View Component):

<form asp-page-handler="ChangeProject" method="post">

    @Html.AntiForgeryToken()
    @Html.DropDownList("id", new SelectList(Model, "Id", "Id"), new { onchange = "this.form.submit()" })

</form>

Thanks in advance!!

Brusque answered 16/4, 2018 at 11:45 Comment(0)
A
2

I exprienced the same problem and the way i fixed it is already answered in your question.

The form call is made at the page where you got your View Component embedded. I don't think it would be even possible to call a handler in your View Component with asp-page-handler as this is Razor Pages tag helper.

The way i got it work is simply putting the page-handler method on the PageModel that is embedding the View Component. In your case you can simply implement this handler on your Razor Page:

public IActionResult OnPostChangeProject()
{
    // ... do Something
}

I don't know though how it would work to trigger a controller method in your View Component. Possibly create a new Controller class and route to it with asp-controller and asp-action in your form tag.

Ascensive answered 24/10, 2018 at 11:58 Comment(0)
R
1

You should remember that the Page handlers could be viewed as convenience methods. All the ASP.Net Core framework does is looks at the Query string parameters and Form data and translates it into Page handler calls. And even though the Handlers are not available in View Components or Partial Views you still can get access to all the required ingredients by injecting IHttpContextAccessor into the View. It will provide you with HttpContext.Request which contains both the Query and the Form properties.

You can then create your own Handler mapper. Here is one, for example:

    public class HandlerMapping
    {
        public string Name { get; set; }
        public System.Delegate RunDelegate { get; set; }

        public HandlerMapping(string name, Delegate runDelegate)
        {
            RunDelegate = runDelegate;
            Name = name;
        }
    }


    public class PartialHandlerMapper
    {
        IHttpContextAccessor _contextAccessor;

        public PartialHandlerMapper(IHttpContextAccessor contextAccessor)
        {
            _contextAccessor = contextAccessor;
        }


        public void RouteHandler(List<HandlerMapping> handlerMappings, string PartialDescriminatorString = null)
        {
            var handlerName = _contextAccessor.HttpContext.Request.Query["handler"];
            var handlerMapping = handlerMappings.FirstOrDefault(x => x.Name == handlerName);

            if (handlerMapping != null)
            {
                IFormCollection form;
                try
                {
                    form = _contextAccessor.HttpContext.Request.Form;
                }
                catch 
                {
                    return;
                }

                if (!string.IsNullOrWhiteSpace(PartialDescriminatorString) && form[nameof(PartialDescriminatorString)] != PartialDescriminatorString)
                    return;

                List<Object> handlerArgs = new List<object>();

                var prmtrs = handlerMapping.RunDelegate.Method.GetParameters();

                foreach (var p in prmtrs)
                {
                    object nv = null;

                    var formValue = form[p.Name];

                    if (!StringValues.IsNullOrEmpty(formValue))
                    {
                        try
                        {
                            nv = TypeDescriptor.GetConverter(p.ParameterType).ConvertFromString(formValue);
                        }
                        catch (FormatException)
                        {
                            //throw new FormatException($"Could not cast form value '{formValue}' to parameter {p.Name} (type {p.ParameterType}) of handler {handlerName}. Make sure you use correct type parameter. ");
                            nv = Activator.CreateInstance(p.ParameterType);
                        }
                        catch (ArgumentException)
                        {
                            nv = Activator.CreateInstance(p.ParameterType);
                        }
                    }
                    else
                        nv = Activator.CreateInstance(p.ParameterType);

                    handlerArgs.Add(nv);
                }

                handlerMapping.RunDelegate.DynamicInvoke(handlerArgs.ToArray());
            }

        }
    } 

And inject it into the service container:

services.AddScoped<PartialHandlerMapper>();

And here is a shopping cart partial view code section example:

@inject ShoppingManager shoppingManager
@inject PartialHandlerMapper partialHandlerMappping

@{
    string ToggleCartItemTrialUseHandler = nameof(ToggleCartItemTrialUseHandler);
    string DeleteCartItemHandler = nameof(DeleteCartItemHandler);

    List<HandlerMapping> handlerMappings = new List<HandlerMapping> {
            new HandlerMapping (ToggleCartItemTrialUseHandler, (Guid? PicID, bool? CurrentValue) => {
                if (PicID == null || CurrentValue == null)
                    return;

                shoppingManager.UpdateTrial((Guid)PicID, !(bool)CurrentValue);
            }),
            new HandlerMapping (DeleteCartItemHandler, (Guid? PicID) => {
                if (PicID == null)
                    return;

                shoppingManager.RemoveProductFromCart((Guid)PicID);
            })
    };

    partialHandlerMappping.RouteHandler(handlerMappings);

    var cart = shoppingManager.GetSessionCart();
    
}

Form element example from the same view:

<td align="center" valign="middle">
   <form asp-page-handler="@DeleteCartItemHandler">
      <input name=PicID type="hidden" value="@i.PicID" />
      <button>
         Delete
      </button>
   </form>
</td>

Where @i is an Item in the shopping cart

Raasch answered 29/8, 2022 at 14:45 Comment(0)
T
0

It's possible to create a combo (Controller/ViewComponent) by decorating the controller with a ViewComponent(Name="myviewcomponent").

Then create the invokeasync as usual, but because the controller doesn't inherit from a ViewComponent, the return result would be one of the ViewComponent result (ViewViewComponentResult, et).

The form in the viewcomponent can then have a button with asp-controller/action tag helpers targetting the controller/action.

Torticollis answered 27/4, 2020 at 22:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.