roo
roo

Reputation: 139

How to pass a value from view (that does not redirect) to another view in ASP.NET Core MVC

So I have a several indexes that share the same edit view, in order to let the Edit post method decide which index view should redirect the user to after the submission, i will pass the name of the page to the Edit view and then i will save it in a variable, which will be used in an if statement later.

So i have this edit button, the value saved in the page in the current page name.

<td>
    <a asp-area="" asp-controller="User" asp-action="Edit" asp-route-id="@item.Id" 
      name="Page" 
      value="@System.IO.Path.GetFileNameWithoutExtension(ViewContext.View.Path)" 
      class="btn btn-primary">Edit</a>
</td>

the editUserViewModel

public class EditUserViewModel
{ 
    public string Id { get; set; }

    [Required]
    [Display(Name = "Name")]
    public string Name { get; set; }

    public string Page { get; set; } //additional property
}

Edit get example

public async Task<IActionResult> Edit(string id)
{
    var user = await _userManager.FindByIdAsync(id);
    if (user == null)
    {
        ViewBag.ErrorMessage = $"User with Id = {id} cannor be found";
        return View("NotFound");
    }
    EditUserViewModel model = new EditUserViewModel
    {
        Name = user.Name,
    };
    return View(model);
}

Edit post

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(EditUserViewModel model)
{
    var user = await _userManager.FindByIdAsync(model.Id);
    if (user == null)
    {
        ViewBag.ErrorMessage = $"User with Id = {model.Id} cannot be found";
        return View("NotFound");
    }
    else
    {
        user.Name = model.Name;

        var result = await _userManager.UpdateAsync(user);

        if (model.Page == "Index1")
        {
            return RedirectToAction("Index1", "User");
        } else if (model.Page == "Index2")
        {
            return RedirectToAction("Index2", "User");
        }
    }
    return View(model);
}

finally the Edit View

<form method="post" asp-action="Edit">
    <h4>Edit</h4>
    <hr />
    <input type="hidden" asp-for="Id" />
    <input type="hidden" asp-for="Page" /> //i want to save the previous page name in this property
    @*<div asp-validation-summary="ModelOnly" class="text-danger"></div>*@
    <div class="form-group">
        <label asp-for="Id"></label>
        <input asp-for="Id" class="form-control" />
        <span asp-validation-for="Id" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Name"></label>
        <input asp-for="Name" class="form-control" />
        <span asp-validation-for="user.name" class="text-danger"></span>
    </div>
    <button type="submit" class="btn btn-primary">Update</button>  @*wrong place*@
    <a asp-action="Index" class="btn btn-success form-control">Back to List</a> //default redirect
</form>

So, how can i redirect the page name from the index to the edit view and save it with the other information?

I would like to thank David Liang and mj1313 for the answers, both worked so well for my case.

Upvotes: 0

Views: 595

Answers (2)

David Liang
David Liang

Reputation: 21476

What about doing it like your login screen, with something like returnUrl that you can use to redirect back after the form post?


You can build a little extension method to get the current URL, with query string:

using Microsoft.AspNetCore.Http;

namespace DL.SO.ViewToView.WebUI.Extensions
{
    public static class HttpRequestExtension
    {
        public static string GetCurrentAbsoluteUrl(this HttpRequest request)
        {
            return $"{ request.Scheme }://{ request.Host }{ GetCurrentRelativeUrl(request) }";
        }

        public static string GetCurrentRelativeUrl(this HttpRequest request)
        {
            return $"{ request.Path }{ request.QueryString }";
        }
    }
}

-- Update --

Here I gave out 2 extension methods to get either the absolute Url, with scheme and host, and the relative url.

Previously OP's author mentioned that a Url like "https://localhost:44376/User/Index" is not considered as local url by IUrlHelper.IsLocalUrl(). And I looked up Microsoft's documentation and confirmed that is indeed the case. So within the app, it will use GetCurrentRelativeUrl() instead.

----


Then your edit button link will become

<a asp-area="" asp-controller="user" asp-action="edit" asp-route-id="@item.Id"
   asp-route-returnUrl="@Context.Request.GetCurrentRelativeUrl()" class="btn btn-primary">
    Edit
</a>

You can place this button basically on any page, as long as you supply @item.Id.

Remember to include the namespace of the extension method in the _ViewImports.cshtml.

enter image description here

See the screenshot? It captures the current URL correctly, with or without query strings!


This returnUrl will be passed in as one of the query string parameters, just like the id:

public async Task<IActionResult> Edit(string id, string returnUrl)
{
    var user = await _userManager.FindByIdAsync(id);
    if (user == null)
    {
        ViewBag.ErrorMessage = $"User with Id = {id} cannot be found";
        return View("NotFound");
    }

    var vm = new EditUserViewModel
    {
        Id = id,
        Name = user.Name,
        ReturnUrl = returnUrl
    };
    return View(model);
}

Of course, your view model will need to be changed to:

public class EditUserViewModel
{ 
    [Required]
    public string Id { get; set; }

    [Required]
    public string Name { get; set; }

    public string ReturnUrl { get; set; }
}

And your form just needs to save it as hidden input, just like how you would do with the Id:

<form method="post" asp-area="" asp-controller="user" asp-action="edit">
    <h4>Edit</h4>
    <hr />
    <input type="hidden" asp-for="Id" />
    <input type="hidden" asp-for="ReturnUrl" />

    ...
</form>

Finally, on the form post, after you're done with the logic, you just need to redirect to returnUrl, as long as it's a valid local URL:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(EditUserViewModel vm)
{
    var user = await _userManager.FindByIdAsync(vm.Id);
    if (user == null)
    {
        ViewBag.ErrorMessage = $"User with Id = {vm.Id} cannot be found";
        return View("NotFound");
    }
       
    user.Name = vm.Name;

    var result = await _userManager.UpdateAsync(user);

    if (Url.IsLocalUrl(vm.ReturnUrl))
    {
        return Redirect(vm.ReturnUrl);
    }

    return View(model);
}

Upvotes: 1

mj1313
mj1313

Reputation: 8459

Add a asp-route-page attribute to your a link:

<a asp-area="" asp-controller="User" asp-action="Edit" asp-route-id="@item.Id" 
  asp-route-page="@System.IO.Path.GetFileNameWithoutExtension(ViewContext.View.Path)" 
  class="btn btn-primary">Edit</a>

Then change Edit get method, add a receive parameter page:

public async Task<IActionResult> Edit(string id, string page)
{
    var user = await _userManager.FindByIdAsync(id);
    if (user == null)
    {
        ViewBag.ErrorMessage = $"User with Id = {id} cannor be found";
        return View("NotFound");
    }
    EditUserViewModel model = new EditUserViewModel
    {
        Name = user.Name,
        Page = page
    };
    return View(model);
}

Upvotes: 1

Related Questions