Getting 404 for HttpPost Action - c#

I have a table of records being displayed in a partial view and some of them have no ID values. So I am trying to use an alternative field as an ID when clicking the Edit link for a particular record. I'm not sure if I can legally have two Post action methods, even though I am using different methods names and params.
Currently, if I click on a record with an ID the correct action method gets called. If I select a record with no ID (instead using an "account" string ID which is unique), I get a 404.
RouteConfig:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Partial View:
...
<td>
#if (item.ID != null)
{
#Html.ActionLink("Edit", "EditBudget", new { id = item.ID })
}
else if (item.Account != null)
{
#Html.ActionLink("Edit", "EditAccountBudget", new { account = item.Account })
}
</td>
BudgetsController:
// POST: Budgets/Edit/5
[Route("edit/{id?}")]
[HttpPost]
public ActionResult EditBudget(int? id = null, FormCollection collection = null)
{
...
// Responding correctly for URL: http://localhost:4007/edit/19
}
[Route("editaccountbudget/{account}")]
[HttpPost]
public ActionResult EditAccountBudget(string account)
{
...
// Getting 404 for URL: http://localhost:4007/editaccountbudget/6000..130
}

ActionLink renders a regular anchor (<a />) tag, so it only does GET not POST. If you want to POST values, you need to use an actual form (either building your own tag, or using Html.BeginForm() ) and then include inside that form's scope a submit button.

Assuming that EditBudget is your controller name , you can change
your route to this to avoid confusing ( or leave as it is since the attribute route will be ignored) and remove [POST] from your action too:
[Route("~EditBudget/EditAccountBudget/{account}")]
Also change:
#Html.ActionLink("Edit", "EditAccountBudget", new new { account = item.Account })
To:
#Html.ActionLink("EditAccountBudget", "EditBudget", new { account = item.Account })
If you use razor pages template controls you need to have both controller and action parts of the route according your route mapping. If you use ajax or httpclient you can have any syntax of route.

Your BudgetsController should look like below without HttpPost Attribute and without Route Attribute as you are using method names in ActionLink. You can use HttpGet attribute if you wish.
Also no need of FormCollection collection parameter in EditBudget method. You will not get anything as its Get not Post.
public ActionResult EditBudget(int? id = null)
{
}
public ActionResult EditAccountBudget(string account)
{
}

As some have pointed out, this is a GET request. If the ID was null, I had to pass the model because I needed more than the account ID to construct the database query.
Partial View:
#if (item.ID != null)
{
#Html.ActionLink("Edit", "EditBudget", new { id = item.ID })
}
else if (item.Account != null)
{
#Html.ActionLink("Edit", "EditBudget", new { account = item.Account,
SelectedDepartment = item.SelectedDepartment, SelectedYear = item.SelectedYear })
}
BudgetsController:
// GET
public ActionResult EditBudget(int? id, BudgetsViewModel model)
{
repo = new BudgetDemoRepository();
if (id != null)
{
// Pass id to repository class
}
else
{
// Pass account and other params to repository class
}
return View(...);
}

Related

MVC controller method for View with optional Guid Parameter

I am trying to make a MVC view accessible from either directly going to that View from a menu or by clicking on a link that'll take me to that same view but with a parameter and with that particular links information instead of seeing a general page if I went straight to it.
public ActionResult Roles(Guid applicationId)
{
if (applicationId == Guid.Empty)
{
return View();
}
var application = new ApplicationStore().ReadForId(applicationId);
return View(application);
}
I know for optional parameters you I'd do something like Guid? in the parameters but visual studios doesn't like that and I can't do Guid application = null either. Any Ideas?
As you already mentioned, make the parameter optional.
public ActionResult Roles(Guid? id) {
if (id == null || id.Value == Guid.Empty) {
return View();
}
var application = new ApplicationStore().ReadForId(id.Value);
return View(application);
}
This also assumes the default convention-based route
"{controller}/{action}/{id}"
Where the id is optional in the route template.
id = UrlParameter.Optional
For example
routes.MapRoute(
name: "SomeName",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Account", action = "Index", id = UrlParameter.Optional }
);
You could potentially just change the Guid parameter to string.
Guid.Empty = 00000000-0000-0000-0000-000000000000 which may cause issues when trying to pass in a null value.
if you switch it to something like this (but still use a Guid):
public ActionResult Roles(string applicationId)
{
if (string.IsNullOrEmpty(applicationId))
{
return View();
}
var application = new ApplicationStore().ReadForId(applicationId);
return View(application);
}
it may side-step the errors you're encountering.

UrlHelper in Controller Action does not build correct URL

I have an issue, when I'm trying to build the url for action in controller, the value of vm after assigning is "/". If I try to create url with other action name then everything works fine, like Url.Action("Edit", "Contact").
public class ContactController : Controller
{
public ActionResult List()
{
string vm = Url.Action("Create", "Contact"); // equals "/"
string editUrl = Url.Action("Edit", "Contact"); // all is fine
return View("List", vm);
}
public ActionResult Create()
{
return HttpNotFound();
}
public ActionResult Edit()
{
return HttpNotFound();
}
}
What's wrong with that code?
It is because your route specifies them as defaults.
Your route is:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Contact", action = "Create", id = String.Empty }, null);
Essentially, it is because you specify the default values controller = "Contact", action = "Create". When you specify these as default you are saying if the value is not provided in the URL then use these.
For examples all these URLs are the same: /, /Contact & /Contact/Create. By default MVC generates you the shortest URL.
You could either change the default values or remove them like this:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { id = String.Empty }, null);

How to map model data in URL route?

I have an index page which displays Categories (which have attributes: id and name) with the URL request: http://localhost:62745/home/index.
When I click a category, I am taken to http://localhost:62745/Home/Products/6.
I want to make the URL more detailed and replace the Category Id property 6 in the previous URL with the name property of the Category which was just clicked.
My route config look like this:
public class RouteConfig
{
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 }
);
routes.MapRoute(
name: "Categories",
url: "{controller}/{action}/{categoryName}",
defaults: new { controller = "Category", action = "Index", categoryName = UrlParameter.Optional }
);
}
}
The first MapRoute() method was already implemented. I added the second one hoping to solve my problem, but it didn't.
This is my controller action for products:
// GET: Product
public async Task<ActionResult> Index(int? id, string categoryName)
{
var products = (await db.Categories.Where(c => c.Id == id)
.SelectMany(p => p.Products.Select(x => new ProductViewModel { Id = x.Id, Name = x.Name, ByteImage = x.Image, Price = x.Price}))
.ToListAsync());
categoryName = db.Categories.Where(c => c.Id == id).Select(c => c.Name).ToString();
if (products == null)
{
return HttpNotFound();
}
return View(new ProductIndexViewModel{ Products = products, CategoryId = id });
}
Both your routes are identical in terms how they are interpreted - they accept 2 segments and an optional 3rd segment, so Home/Products/6 and Home/Products/CategoryName both match the first Default route (there is nothing unique to distinguish the 2nd Categories route so it will never be hit.
Your question refers to a Products() method in the 2nd url, but you have not shown that method, so its not clear if your referring to a product name or a category name, but the principal is the same.
Assuming you want the url to be Home/Products/ProductName to display a specific product, then in the view
#foreach(var item in Model.Products)
{
// link for each product
#Html.ActionLink("Details", "Products", "Home", new { id = item.Name }, null)
Then you can simply make the method
public ActionResult Products(string id)
{
// the value of id is the the the product name
....
}
Alternatively, if you want the parameter to be string name rather that string id, then you can define a specific route and place it before the Default route.
routes.MapRoute(
name: "ProductDetails",
url: "Home/Products/{name}",
defaults: new { controller = "Home", action = "Products", name = UrlParameter.Optional }
);
so that the method becomes
public ActionResult Products(string name)
Side note: The fact you have a query in the Index() method to get the category name suggests you might also be wanting a 'slug' route, in which case, refer how to implement url rewriting similar to SO.

How to connect a Form with Controller in MVC3?

So, I am trying to submit a form on a List Page(http://example.com:3480/List) which is actually a Search implementation. So far I have done this:
index.cshtml
#using(Html.BeginForm("Search","ListController"))
{
<input id=query type=text name=query />
<input id=btnsearch type=submit value=Search />
}
ListController.cs
[HttpPost]
public ActionResult Search(FormCollection collection)
{
Response.Write("We are here");
// Get Post Params Here
string var1 = collection["query"];
Response.Write(var1);
return View();
}
Global.asax
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Details",
"Details/{id}/{orderid}",
new { controller = "Details", action = "Index", id = UrlParameter.Optional, orderid = UrlParameter.Optional }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional} // Parameter defaults
);
}
Upon Clicking it goes to http://example.com:3480/ListController/Search which seems fine.
Now I guess I need to define route in Global.aspx but not sure. What I want is to show result in same View file instead of creating a new one.
At this moment I am unable to get into Search method after POSTing form
Assuming you are currently just using the default route, the reason you are not reaching the action method is that the "Controller" suffix on your route is implicit - it shouldn't be part of your URL.
#using(Html.BeginForm("Search","List"))
Additionally, regarding:
What I want is to show result in same View file instead of creating a new one.
You can easily return a specific view from any controller action by specifying the name of the view in the call to the View method:
return View("Index");

"The parameters dictionary contains a null entry for parameter" - How to fix?

I am trying to implement an edit page in order administrator to modify data in database.Unfortunately I am encountering an error.
The code below:
public ViewResult Edit(int productId) {
// Do something here
}
but I am getting this error:
"The parameters dictionary contains a null entry for parameter 'productId' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ViewResult Edit(Int32)' in 'WebUI.Controllers.AdminController'. To make a parameter optional its type should be either a reference type or a Nullable type.
Parameter name: parameters"
I changed my route in Global.asax.cs like this:
routes.MapRoute(
"Admin",
"Admin/{action}/{ productId}",
new { controller = "Admin", action = "Edit", productId= "" }
);
but still I am getting the error .
That empty string for productId (in your default route) will get parsed to a null entry by the Framework, and since int does not allow null...you're getting the error.
Change:
public ViewResult Edit(int productId)
to
public ViewResult Edit(int? productId)
if you want to allow for the caller to not have to pass in a product id, which is what it looks like what you want to do based on the way your route is configured.
You could also re-configure your default route to pass in some known default for when no productId is supplied:
routes.MapRoute(
"Admin",
"Admin/{action}/{ productId}",
new { controller = "Admin", action = "Edit", productId= -1 }
I came across the same problem following the worked SportStore example in Pro ASP.Net
The solution was actually that my Index view has the following code.
#Html.ActionLink("Edit", "Edit", new { id=item.ProductID }) |
However my Edit method in my controller was defined as
public ViewResult Edit(int productId)
changing my Index view to read
#Html.ActionLink("Edit", "Edit", new { productId=item.ProductID }) |
fixed the problem
Here is the way how to ignore such argument errors for any controller method invoke:
public class MyControllerBase
{
//...
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Exception != null)
{
var targetSite = filterContext.Exception.TargetSite;
if (targetSite.DeclaringType != null)
if (targetSite.DeclaringType.FullName == typeof(ActionDescriptor).FullName)
if (targetSite.Name == "ExtractParameterFromDictionary") // Note: may be changed in future MVC versions
{
filterContext.ExceptionHandled = true;
filterContext.Result = new HttpStatusCodeResult((int)HttpStatusCode.BadRequest);
return;
}
//...
}
// ...
}
}
productId should be constrained to int type.
new {controller="Admin", action="Edit"},
new {productId = #"\d+" }
Perhaps you forgot to pass the required data (in this case the 'productId') in the View.
I assume you try to access the detail by clicking the link in the index page which I also assumed it as "View\Admin\index.cshtml"
<td>
#Html.ActionLink("Edit", "Edit", new { productId = item.ProductId }) |
#Html.ActionLink("Details", "Details", new { productId = item.ProductId }) | //note the productId is filled by item.ProductId
#Html.ActionLink("Delete", "Delete", new { productId = item.ProductId })
</td>
Fail to do so will result all params to be null thus cause the error.
Changing it on the route could implicate other routes that use the same url. To keep it simple and cover all bases.
[HttpGet]
public ActionResult ThePage(int id = -1)
{
if(id == -1)
{
return RedirectToAction("Index");
}
//Or proceed as normal
}
This is also great if this is an Error you are getting when you access the page because you are required to have an id, (for instance... people putting the url in the address bar when they are not supposed to) then set the parameter to an optional value.
EDIT: sorry int, not long as the question asked
the standard "int" class (int32) does not accept null values, and it will fail a cast from an empty string to int in that case and try and assign null to it.
I would probably take a look at what you're trying to accomplish -- if you're trying to force the administrator to have a productID present for them to edit that record in the database, I would think about putting that into the Request object or some other method that will give some more versatility.

Categories