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.
Related
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).
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
I have a ToDo items dashboard page where I display the ToDo's, their status and some other app info.
I want to have one input where I can add a string value (the ToDo title) and on the button click
have that passed to the controllers Create get method, so it populates the Create views Title input field with that value.
I want to it without a form if that is possible as the dashboard page already has a model which is an IEnumerable, just pass that value as a querystring parameter to the Create pages get view (or is it doable in javascript?).
Im not an MVC expert and also not as familiar with the new tag helper methodologies. Any help in how to structure thiswould be helpful.
Here is the html
<!-- Add Task -->
<div class="input-group input-group-lg">
<input class="form-control" type="text" placeholder="Add task and press enter..">
<span class="input-group-addon">
<a asp-controller="ToDoItems" asp-action="Create" ><i class="fa fa-plus"></i></a>
</span>
</div>
<!-- END Add task -->
here is the new model
public Class MyModel{
public IEnumerable<your old model> Old Model {get; set;}
public string Title {get;set;}
}
You can create a form like so in html with razor syntax
#model MyModel
...
<form action="/Controller/PostTitle/" method="post">
#Html.TextBoxFor(m => m.Title,new {#class = "...", #placeholder="...",
#requried="required"})
<input id="export-btn" type="submit" value="Download" class="btn btn-info" />
</form>
The #TextBoxFor will create a textbox and the lambda lets you use your strongly typed model.
Here is the controller
[HttpPost]
public IActionResult PostTitle(string Title) {
...
}
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
I have a bootstrap modal which has many buttons which help to download files of different formats. I am able to enter the controller method when I use the set the onclick function as below:
onclick="location.href='#Url.Action("DownloadAsJPG", "Home")'"
I would like to do some condition based file downloading, based on the button that was pressed and hence I was thinking of passing a parameter as done here and here by setting the value attribute of the buttons
HTML :
<button type="button" id="tojpg" class="btn btn-outline-primary" value="jpg">JPG</button>
<button type="button" class="btn btn-outline-primary" value="jpgcmyk">JPG-CMYK</button>
<button type="button" class="btn btn-outline-primary" value="jpgrgb">JPG-RGB</button>
The argument in the controller method always remains null. I'm not sure what I have missed.
Controller method:
public FileResult DownloadAsJpg(string argument)
{ Some action }
I tried to play with a jquery which I found on a stackoverflow question which doesn't help me either, I couldn't reach the controller using this jquery.
Jquery
$('#tojpg').click(function (e) {
e.preventDefault();
window.location = '/Home/DownloadAsJpg?argument=' + $('#tojpg').val();
});
Any tips would be greatly appreciated.
If you can reach your controller with
onclick="location.href='#Url.Action("DownloadAsJPG", "Home")'"
and just want to pass some parameters. You can do that same was as
onclick="location.href='#Url.Action("DownloadAsJPG", "Home", new { argument = "tojpg" })'"
or with help of Jquery event
Edit
Try to wrap your event into $(document).ready(). By my experience, most of the time the reason for not working events is a that your buttons is not yet created when event binding happends.
$(document).ready(function() {
$('#tojpg').click(function (e) {
e.preventDefault();
location.href = '#Url.Action("DownloadAsJPG", "Home", new { argument = "tojpg" })';
});
}
And if you dont want to write a separate event for each button option you can create something like this.
<button type="button" class="btn btn-outline-primary" value="jpg">JPG</button>
<button type="button" class="btn btn-outline-primary" value="jpgcmyk">JPG-CMYK</button>
<button type="button" class="btn btn-outline-primary" value="jpgrgb">JPG-RGB</button>
and Jquery event like this
$(document).ready(function() {
$('.btn').click(function () {
location.href = '#Url.Action("DownloadAsJPG", "Home", new { argument = "'+ $(this).attr("value") +'" })';
});
}
That should work.
There are two ways of solving this:
Option 1
A <button /> is not part of the data that the form is posting. That is why it doesn't turn up at the server side. You should change this into an input like so:
<input type="submit" name="argument" value="jpg" />
The name of this field should be the same one as the name of the parameter in your action. Because this is an input-field, the browser will send the it with the entire post. This is what is being done in the posts you referred to.
Option 2
If you want to use window.location instead, then you need to make sure the action allows for a GET-request and that you pass in argument as the querystring:
onclick="location.href='#Url.Action("DownloadAsJPG", "Home", new { argument = "jpg" })'"