Model from PartialView to Controller returns null - c#

I have a PartialView that recibes a Model ID and then when you click on the link sends the Model ID to the Controller. My problem was that there were 2 ways to send data from View to Controller but just only one worked for me. What's the reason why?
This worked perfectly for me
<a type="button" class="btn btn-primary" asp-controller="Dictionary" asp-action="Edit" asp-route-wordId="#Model"><i class="fas fa-edit"></i></a>
This one didn't work
<a type="button" class="btn btn-success" href="#Url.Action("Edit/" + Model)"><i class="far fa-list-alt"></i></a>
My question is why the last one didn't work and returned null if I am passing the Model ID to the URL
This is my full PartialView that receives a Model ID and works
#model int
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<td style="width:150px">
<div class="btn-group" role="group">
<a type="button" class="btn btn-primary" asp-controller="Dictionary" asp-action="Edit" asp-route-wordId="#Model"><i class="fas fa-edit"></i></a>
</div>
</td>
And this is my Controller Function that recibes the data
public async Task<IActionResult> Edit(int? wordId)
{
if(wordId == null)
{
return NotFound();
}
var word = await _db.Dictionaries.FindAsync(wordId);
if(word == null)
{
return NotFound();
}
return View(word);
}

The Url.Action overload you are using takes in an action name and you are passing something like /Edit/4 to it. You need to use the proper overload of the Url.Action method that allows you to specify route values.
It would look something like the following:
<a type="button" class="btn btn-success" href="#Url.Action("Edit/" + Model)"><i class="far fa-list-alt"></i></a>
to
<a type="button" class="btn btn-success" href="#Url.Action("Edit", new { wordId = Model } )"><i class="far fa-list-alt"></i></a>`
As a side note, your Edit action takes a nullable wordId which doesn't make much sense to me. Generally, if you're editing something then it has already been created and should always have an id. The only case I can think of where this makes sense is if the Edit action actually handles creating data as well as editing data in which case that would be perfectly valid.

To answer your main question, the reason your 1st anchor is working is because the args are correct.
For example:
<a asp-controller="Dictionary" asp-action="Edit" asp-route-wordId="1">Your link</a>
is equivalent to:
That works!
The second will not work because you are misusing Url.Action, the method you are using is this Url.Action("Action")
Url.Action("Edit/" + Model) or Url.Action("Edit/1")
is searching for an action named: "Edit/1", which in your case will not be found.
so in your 2nd not working example
Your Link
is equivalent to:
<a href="null" />
In your case you should be using this in your anchor:
Url.Action("Edit", "Dictionary", new { id = #Model })
is equivalent to
/Dictionary/Edit/1
so:
Your link

Related

No button links

Trying to deal with buttons with C# Core MVC
But I still don't understand the logic.
Help me please.
Here I have a controller method (GroupsController)
public ActionResult AgentParams(int? groupID, int? packageID)
{
return ApiResult(new GroupAgentParamsModel(groupID, packageID).LoadData(Dao));
}
I can use it via button.
<a class="btn btn-default" href="#Url.RouteUrl("GroupAgentParams", new { groupID = Model.Group.DataId })">Agent Params</a>
this button has a link:
https://localhost:5001/Group/2/AgentParams
But there are other controller methods
But I can call like above method I can't.
Here is an example (GroupsController):
public ActionResult WorkModes(int? id)
{
return ApiResult(LoadData(id.Value));
}
button:
<a class="btn btn-default" href="#Url.RouteUrl("GroupWorkModes", new { id = Model.Group.DataId })"></a>
this button has no link and doesn't work at all.
why?
Trying to deal with buttons with C# Core MVC
Could you try Anchor Tag Helper in ASP.NET Core ? The attribute names are prefixed with asp- .
<a class="btn btn-default" asp-controller="Groups"
asp-action="AgentParams"
asp-route-groupID=1> Agent Params</a>
<a class="btn btn-default" asp-controller="Groups"
asp-action="WorkModes"
asp-route-id=1> Work Modes</a>
result:
Read Anchor Tag Helper in ASP.NET Core to know more.

ASP.NET paging is reset with multiple forms

My web interface has a control panel built in Razor Pages, showing varius devices admins can configure, displayed in a table with varius buttons on the side for quick actions and info (like a toggle to show enabled and connection status).
The way i built it initially it had no paging, and a simple loop with a forms column for quick actions, based on the relative row
#{
ViewBag.BaseLayout = "/Views/Shared/_Layout_Areas.cshtml";
Layout = "/Views/Shared/_Layout_IPageable.cshtml";
}
#* other code bla bla bla *#
#foreach (var device in #Model.Devices)
{
<tr>
<td class="forms-inline">
<form id="entrydelete" method="post" onsubmit="return _formconfirm();">
<button class="btn btn-danger btn-sm" type="submit" asp-page-handler="delete" asp-route-id="#device.Id">
<i class="bi bi-trash3"></i>
</button>
</form>
<form id="useredit" method="post">
<button class="btn btn-warning btn-sm d-inline" type="submit" asp-page-handler="update" asp-route-id="#device.Id">
<i class="bi bi-pencil-square"></i>
</button>
</form>
<form id="toggle" method="post" target="_self">
<button class="btn #(device.Enabled ? "btn-success" : "btn-secondary" ) btn-sm d-inline" type="submit" asp-page-handler="toggleEnabled" asp-route-id="#device.Id">
#if (device.Enabled)
{
<i class="bi bi-toggle-on"></i>
}
else
{
<i class="bi bi-toggle-off"></i>
}
</button>
</form>
</td>
<td>#Html.Label("Description",device.Description ?? "---")</td>
<td class="font-monospace w-space-pre">#Html.Label("Ip",device.Ip)</td>
<td class="font-monospace">#Html.Label("Port",device.Port.ToString())</td>
<td>#Html.Label("Organization",device.OwnedBy?.Name)</td>
<!-- etc. etc. -->
</tr>
}
Now this works great if you have no paging and stuff... But later on as devices grew exponentially a paging solution needed implementation.
So i added an IPageable interface and implemented extension methods and stuff, but i added an additional layout Layout_IPageable.cshtml adding the paging controls at the bottom of the page in the classic
[<-] [1] ... [n-1] [n] [n+1] ... [nmax] [->]
for reusability and style consistency across multiple pages, but i made it a form for general filtering for specific pages
_Layout_IPageable.cshtml
#model IPageable
#{
Layout = ViewBag.BaseLayout ?? "/Views/Shared/_Layout.cshtml";
}
<!-- render body and styles and stuff -->
<div class="paging form text-end">
<label asp-for="Paging.PageNumber" class="form-label"></label>
#if (Model.Paging.PageNumber > 1)
{
<button class="btn btn-sm btn-secondary" onclick="navigatePaging(-1)"><i class="bi bi-arrow-left"></i></button>
}
else
{
<button class="btn btn-sm btn-outline-secondary" disabled><i class="bi bi-arrow-left"></i></button>
}
<!-- etc. etc. -->
[ <input asp-for="Paging.PageNumber" style="width: min-content" form="search" min="1" max="#Model.Paging.MaxPageCount" onchange="this.form.submit()" />
/#Html.Label(Model.Paging.MaxPageCount.ToString()) ]
<!-- etc. etc. -->
<label asp-for="Paging.Take" class="form-label"></label>
<select asp-for="Paging.Take" form="search" onchange="this.form.submit()">
<option>10</option>
<option>25</option>
<option>40</option>
<option>100</option>
</select>
</div>
interface IPageable
{
DataPagingModel Paging { get; set; }
}
public static class PagingExtensions
{
public static IEnumerable<T> Page<T>( this IEnumerable<T> input, DataPagingModel paging )
{
paging.ResultsCount = input.Count();
paging.MaxPageCount = (input.Count()-1) / paging.Take + 1;
paging.PageNumber = Math.Min(paging.PageNumber, paging.MaxPageCount);
return input
.Skip(paging.Skip)
.Take(paging.Take);
}
}
[ValidateAntiForgeryToken]
public partial class DevicesModel : PageModel, IPageable
{
[BindProperty(SupportsGet = true)]
public DataPagingModel Paging { get; set; }
public IActionResult OnGet()
{
thid.Devices = this._devicesSrvc
.VariusChainedLinqMethods()
.Page(this.Paging);
return this.Page();
}
public async Task<IActionResult> OnPostUpdate( Guid id )
{
/* code to update and stuff ... */
// the problem is here, in non-get methods i cannot figure out
// how to return the correct paging because this.Paging is null!
return this.OnGet();
}
/* etc. etc. */
}
The problem is that now when previus "quick actions" forms are submitted, obviusly the paging form is NOT submitted as well, and the paging resets to the default (so page 0, with default page size and no filters).
How should i go about this?
Implement the previus "quick actions" forms as API calls and then reload the page?
Or is there a more elegant solution?
The solutions was pretty simple actually, because the original query is sent by the browser as an URL in the Referer HTTP Header when submitting any form.
So when sending one of the "post" forms, i expect the referer to be the original GET query for the page.
Given it's not something to completely rely on, but returning this at the end of the varius Post handlers...
protected IActionResult RedirectReferOrReload( )
{
var referer = this.Request.Headers.Referer.SingleOrDefault();
if (string.IsNullOrEmpty(referer))
return this.RedirectToPage();
// this ensures the referer cannot go to a malicius link
return this.Redirect(this.Request.Path.Value + new Uri(referer).Query);
}
public async Task<IActionResult> OnPostUpdate( Guid id )
{
/* code to update and stuff ... */
return this.RedirectReferOrReload();
}
... will redirect to the referer page if the header is present, otherwise just reload the page.
When the page is then reloaded with the correct query the status of the shown items is correctly updated.
Now, this works great for my case because i expect default browser configurations from the target users of this page, so that the Referer header is always sent on forms (i also updated ASP.NET to specify a Referrer-Policy).

.Net Framework C# MVC Spontaneously Redirects to View From Wrong Controller

I have a small project very basic, VS 2013, .Net Framework 4.5 with System.Web.Mvc (5.2.7) installed.
No special routes or authentication; just 3 controllers scaffolded from an EF context. One of the controllers is called DataRoutesController, and another is DataRouteViewFieldsController. DataRouteViewFields is a child (foreign key) of DataRoute. On the Edit page for DataRoute I have a table which lists the related ViewFields, each row with it's own edit and delete buttons.
<button class="btn btn-sm btn-primary"
onclick="location.href='#Url.Action("Edit", "DataRouteViewFields", new { id = item.RouteId, name = item.Name })'"
title="Edit">
<span class="glyphicon glyphicon-pencil"/>
</button>
When I click the button it calls the correct controller and proceeds to the return statement.
public ActionResult Edit(int? id, string name)
{
DataRouteViewField dataRouteViewField = db.DataRouteViewFields.Find(id, name);
return View(dataRouteViewField);
}
But then, for some reason I can't comprehend I calls the DataRouteController Index() route and returns the wrong page.
public ActionResult Index()
{
var dataRoutes = db.DataRoutes.Include(d => d.Connection);
return View(dataRoutes.ToList());
}
This behavior is not observed when I don't use the button. If I type the URL to edit the ViewField manually, it works as expected.
When I step though the code (F11) following the return from DataRouteViewFieldController.Edit(...), it proceeds to _ViewStart, then to the Edit.cshtml for DataRouteViewFieldController like it should. When it finishes Edit.cshtml it renders the wrong page. The Edit.cshtml page is largely blank for troubleshooting purposes:
#model ctNetData.DataRouteViewField
#{
ViewBag.Title = "Edit";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Edit</h2>
If I add a break to DataRoutesController.Index() then the process starts flipping back and forth between lines of code in DataRouteViewFieldsController.Edit(...) and DataRoutesController.Index() with F11, like it's processing two threads at the same time.
Anyone recognize what's going on here?
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
The problem was that my button, despite not being of type "submit" or "input" was inducing the page's form to submit. Moving the button(s) outside the form corrected the behavior.
According to this page, How to prevent buttons from submitting forms <Button/> has a default type of "submit". So there are two remedies. Move the foreign key table outside of the form, or specify the button's type as button.
#using (Html.BeginForm())
{
//Button submits form, has behavior described above
<button class="btn btn-xs btn-primary" onclick="location.href='#Url.Action("Edit", "DataRouteViewFields", new { id = item.RouteId, name = item.Name })'" title="Edit">
<span class="glyphicon glyphicon-pencil" />
</button>
//Button below works as expected
<button type="button" class="btn btn-xs btn-primary" onclick="location.href='#Url.Action("Edit", "DataRouteViewFields", new { id = item.RouteId, name = item.Name })'" title="Edit">
<span class="glyphicon glyphicon-pencil" />
</button>
}
//Button works as expected
<button class="btn btn-xs btn-primary" onclick="location.href='#Url.Action("Edit", "DataRouteViewFields", new { id = item.RouteId, name = item.Name })'" title="Edit">
<span class="glyphicon glyphicon-pencil" />
</button>

ASP net core MVC Call diferent actions under 1 form

I have my this in my controller:
[HttpPost]
public IActionResult Save(int IdentifikaceZ, ReklamaceModel model)
{
_db.Add(model);
_db.SaveChanges();
return RedirectToAction("MyMainView");
}
#using (Html.BeginForm("Save", "MyMainView", FormMethod.Post))
{
....
<input id="Insert" name="Insert" value="Insert" type="submit">
<input id="Edit" name="Edit" value="Edit" type="submit">
}
What I need is to Save/Edit info inside Form but I dont know how to tell server to decide which to do. So when I click button A or button B it does same thing. I need it to do seperate things but with same elemetns (elements inside form) Thanks for any help.
The buttons are named, and the way named buttons work is that only the one that is clicked makes it into the POST. As such, you can simply check for the presence of one key or the other in the form data:
if (Request.Form.ContainsKey("Insert"))
// do insert
if (Request.Form.ContainsKey("Edit"))
// do edit
First, add a property string ActionType to your ReklamaceMode model.
Then change your HTML to:
#using (Html.BeginForm("Save", "MyMainView", FormMethod.Post))
{
....
<input id="Insert" name="ActionType" value="Insert" type="submit">
<input id="Edit" name="ActionType" value="Edit" type="submit">
}
Now in your csharp code:
[HttpPost]
public IActionResult Save(int IdentifikaceZ, ReklamaceModel model)
{
// check model.ActionType, it will be either Insert or Edit
}
Add and update operations can be handled with one form and one method as well.
Add id as hidden form control, for existing records this field will have a value which is the relevant record id. For new records the value will be 0 by default (assuming id type is int).
#using (Html.BeginForm("Save", "MyMainView", FormMethod.Post))
{
....
#Html.HiddenFor(x => x.Id)
<input type="submit" name="submit" value="Save" />
}
on the backend check for the id value, if it is > 0 then you trigger update, otherwise it is a new record.
[HttpPost]
public IActionResult Save(int IdentifikaceZ, ReklamaceModel model)
{
if(model.Id > 0)
_db.Update(model);
else
_db.Add(model);
_db.SaveChanges();
return RedirectToAction("MyMainView");
}
That was a simplified implementation, in a real life project you need to do more control over the model before adding/updating. For example it is recommended to use an input model then bind the values to the db model...
if ou still need to use multiple submit buttons in one form to target different backend actions see Multiple Submit Buttons for standard form and ajax forms as well.
Here you can do like this:::
<form method="post" asp-controller="Home">
<a id="Insert" name="Insert" value="Insert" type="submit" asp-action="Delete" asp-route-DeleteID="#model.DeleteID"/>
<a id="Edit" name="Edit" value="Edit" type="submit" asp-action="Edit" asp-route-DeleteID="#model.EditID"/>
</form>
[HttpPost] public IActionResult Delete(int id) {
}
[HttpPost] public IActionResult Edit(int id) {
}

Post form with only input buttons passing parameters to Action in ASP.NET Core

Im working on my thesis work, im making a simple browser game, so far I have a form with 3 buttons that I want to invoke the same action with diffirent parameters this is what i've figured so far:
#using (Html.BeginForm("Gather", "Character", FormMethod.Post, new { #class = "btn-group-vertical mr-2", #role = "group", }))
{
<h3>Woods:</h3>
<input type="submit" class="btn btn-secondary" value="Woods of Deloria (90%)" />
#Html.Hidden("area", "woods")
#Html.Hidden("type", "deloria")
<input type="submit" class="btn btn-secondary" value="Woods of Forgotten souls (50%)" />
#Html.Hidden("area", "woods")
#Html.Hidden("type", "forgotten souls")
<input type="submit" class="btn btn-secondary" value="Shadowforest (10%)" />
#Html.Hidden("area", "woods")
#Html.Hidden("type", "shadowforest")
}
My question is how do i make the diffirent buttons to pass diffirent types, all buttons have to be in same form otherwise it breaks my css. I am using hidden, since I don't want the user to be able to edit the values that are passed as parameters, also I don't want to values to be passed onto the URL.
EDIT: I realized that #Html.Hidden doesn't hide it from the html, what would my approach be if i want to pass parameters to an action from a button, that the user can't edit?
EDIT 2: Alright so i made some progress, changed the form to
<form method="post" class="btn-group-vertical mr-2" role="group">
<h3>Woods:</h3>
<input type="submit" class="btn btn-secondary" name="deloria" value="Woods of Deloria (90%)" />
<input type="submit" class="btn btn-secondary" name="forgotten souls" value="Woods of Forgotten souls (50%)" />
<input type="submit" class="btn btn-secondary" name="shadowforest" value="Shadowforest (10%)" />
</form>
And my post action looks like this:
[HttpPost]
public IActionResult Gather(int id)
{
var taskName = "";
if (Request.Form.ContainsKey("deloria"))
{
taskName = "deloria";
}
else if (Request.Form.ContainsKey("forgotten souls"))
{
taskName = "forgotten souls";
}
else if (Request.Form.ContainsKey("shadowforest"))
{
taskName = "shadowforestD";
}
if (string.IsNullOrEmpty(taskName))
{
return Json("uh oh");
}
else
{
return Json(taskName);
}
}
}
I know it's a mess, but i will find a way to make it a little more compact.
I would take a look at the various options outlined here:
http://www.binaryintellect.net/articles/c69d78a3-21d7-416b-9d10-6b812a862778.aspx
If you are using Razor Pages, and not traditional MVC, you should take a look at handler methods:
https://www.learnrazorpages.com/razor-pages/handler-methods

Categories

Resources