asp.net mvc 5 actionlink 404 error with Area - asp.net

**I have an index.cshtml inside a path like TeacherArea/Home/Index where I have this following actionlink: **
#Html.ActionLink("Edit", "EditTeachers", "Home", new { Area = "TeacherArea", id = item.id }, new { })
The purpose is to edit simple table row data.
My Home Controller looks like this:
namespace DemoProject2MVC.Areas.TeacherArea.Controllers
[RouteArea("TeacherArea")]
[RoutePrefix("TeacherArea/Home")]
public class HomeController : Controller
{
return view();
}
[Route("EditTeachers")]
public ActionResult Edit(int id)
{
MyDBContext stn = new MyDBContext();
Teacher tchr = stn.Teacher.Where(a => a.id == id).FirstOrDefault();
return View(tchr);
}
** I also have set namespace in "TeacherAreaAreaRegistration" of the controller class present inside the area like the following:**
public class TeacherAreaAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "TeacherArea";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"TeacherArea_default",
"TeacherArea/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new[] { "DemoProject2MVC.Areas.TeacherArea.Controllers" }
);
}
}
I also have my global.asax like this:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
DataAccessLayer.StartUpClass.Start();
}
My issue is:
The index action method is calling fine within this mentioned area. But the actionlink to call Edit Action method is throwing 404.
https://localhost:44336/TeacherArea/Home/EditTeachers/7

I may be a bit rusty on MVC, but I think that if you want to mix attribute-based area declarations/routing with area/route registration in startup you need to make sure you are registering stuff in the proper order.
Another thing I noticed was that you're never calling RouteTable.Routes.MapMvcAttributeRoutes(); in your startup -- is that intentional?
If you are using both Areas with route attributes, and areas with convention based routes (set by an AreaRegistration class), then you need to make sure that area registration happen after MVC attribute routes are configured, however before the default convention-based route is set. The reason is that route registration should be ordered from the most specific (attributes) through more general (area registration) to the mist generic (the default route) to avoid generic routes from “hiding” more specific routes by matching incoming requests too early in the pipeline.
MSDN

Maybe you can try Ajax approach..
return $.ajax({
type: 'POST',
url: '<URL you want>',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
data: JSON.stringify({ Id: id}),
success: function (data) {
...
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
...
}
});
And your controller
[HttpPost]
public JsonResult EditTeachers(int Id)
{
MyDBContext stn = new MyDBContext();
Teacher tchr = stn.Teacher.Where(a => a.id == id).FirstOrDefault();
return Json(tchr, JsonRequestBehavior.AllowGet);
}
It might be easier to debug because you can see the where the Ajax request is going from this by inspecting the network.
Hope it helps

Related

Asp.net mvc5 same action POST and GET in the same method

Can i send to the action method new record and then get the new id by the same method
public class HomeController : Controller
{
[HttpGet]
[HttpPost]
public JsonResult _sendConfirmation'(string subject,string mail)
{
Some Code--Some Code---Some Code
return Json(new { Success = true, id = newCreatedMailId });
}
}
Getting the id by jquery
$.getJSON('/Mails/_sendConfirmation', function (comingData) {
alert("success" + data);
jQuery.get('/Mails/_getNewMailSendConfirmation', { id: comingData }, function (data) {
jQuery('#myModal').modal('show');
jQuery('#myModal .modal-body').html(data);
});
enter code here
So your Ajax request is looking for an action named _sendConfirmation on your Mails controller and not finding it but you've chose. To just show us your home controller index action so we have no idea if the appropriate controller exists or not with that.
As far as having it decorated with a get and a post, when you do this you are essentially telling the action to look for data on the request in two different places which will most likely not end well for you unless you plan on doing that switch logic yourself inside the action. Your code would probably be a bit less error prone if you seperated the action and once extracted the data, you call the same helper methods to work your data.

ASP.NET MVC custom routing with default route

I have a bit of problem
I have an MVC site that is working just fine.
I wanted to have SEO friendly urls so I created custom routing that looks like this
{section}/{group}/{manufacturer}
since it has the same number of fields as the default route
{controller}/{action}/{id}
i created in loop in RouteConfig.cs that goes to database and loops Sections table and creates custom routes like
FOOD/{group}/{manufacturer}
SPORT/{group}/{manufacturer}
CARS/{group}/{manufacturer}
and point it to the Sections controller.
It works just fine. The problem I have is that now i need to rename FOOD to FRESH-FOOD and i want to keep old links to FOOD working.
What would you recommend ? How can I solve this ?
Is there a better way to do route Section instead of custom routes ?
Sounds like this might be a good case for a custom route handler. In the ProcessRequest method below you can check for controller name of "FOOD" and redirect to "FRESH-FOOD" like this:
public class CustomRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new CustomHttpHandler();
}
}
public class CustomHttpHandler : IHttpHandler
{
public bool IsReusable
{
get
{
return true;
}
}
public void ProcessRequest(HttpContext context)
{
var routeValues = context.Request.RequestContext.RouteData.Values;
var controllerName = context.Request.RequestContext.RouteData.GetRequiredString("controller");
if (controllerName.ToLower() == "food") controllerName = "FRESH-FOOD";
var controller = ControllerBuilder.Current.GetControllerFactory().
CreateController(context.Request.RequestContext, controllerName);
if (controller != null)
{
controller.Execute(context.Request.RequestContext);
}
}
}
When you create your custom routes use the Route constructor overload that takes an IRouteHandler

Custom Error Message MVC

Working on Project which is mixture of ASP.Net Web Forms & MVC.
Scenario : In an Web Form application , part of an page is rendering using MVC Partial View as shown below image.
In Web Form aspx page , have defined an Div with ID = MVCPartialView1 and using Jquery Ajax successfully can bind the returned Partial View to Web Form.
$.ajax(
{
success: function (msg) { $("#MVCPartialView1").html(msg); }
});
For Handling Error Scenario made use of the following code.
[AttributeUsage(AttributeTargets.Class)]
public sealed class ErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
// Execute the normal exception handling routine
base.OnException(filterContext);
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new ViewResult
{
ViewName = "CustomError.aspx"
};
}
}
}
Base Controller :
[ErrorAttribute]
public class BaseController : Controller
Actual Problem is when an any exception occurred inside MVC Partial View ( Controller ) , CustomError is displayed only inside DIV MVCPartialView1 but it would make sense to show CustomError as full Contain of WebForm.aspx
But Expected CustomError Message is :
You can make it work only with js in your scenario.
Some thing like this:
$.ajax(
{
success: function (msg)
{
if(msg.indexOf("Error") > -1) //You should check if Error page returned
{
window.location.href = "CustomError.aspx"; //Here if redirect to error of full page
}
else
{
$("#MVCPartialView1").html(msg);
}
}
});

How do I return a 404 status where invalid parameters are passed to my ASP.NET MVC controller?

I want to return a HTTP status 404 if invalid arguments are passed to my controller. For example if I have a controller that looks like:
public ActionResult GetAccount(int id)
{
...
}
Then I want to return a 404 if say urls such as these are encountered:
/GetAccount
/GetAccount/notanumber
i.e. I want to trap the ArgumentException that is thrown.
I know I could use a nullable type:
public ActionResult GetAccount(int? id)
{
if(id == null) throw new HttpException(404, "Not found");
}
But that's pretty icky and repetitious.
I was hoping I could add this to my controllers where necessary:
[HandleError(View="Error404", ExceptionType = typeof(ArgumentException))]
public class AccountsController : Controller
{
public ActionResult GetAccount(int id)
{
...
}
}
But that doesn't appear to work well.
I saw this post and this answer which nearly solves my problem:
In that answer an abstract BaseController is created from which you derive all your other controllers from:
public abstract class MyController : Controller
{
#region Http404 handling
protected override void HandleUnknownAction(string actionName)
{
// If controller is ErrorController dont 'nest' exceptions
if (this.GetType() != typeof(ErrorController))
this.InvokeHttp404(HttpContext);
}
public ActionResult InvokeHttp404(HttpContextBase httpContext)
{
IController errorController = ObjectFactory.GetInstance<ErrorController>();
var errorRoute = new RouteData();
errorRoute.Values.Add("controller", "Error");
errorRoute.Values.Add("action", "Http404");
errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
errorController.Execute(new RequestContext(
httpContext, errorRoute));
return new EmptyResult();
}
#endregion
}
This works great at handling unknown actions with a 404 but doesn't allow me to handle invalid data as a 404.
Can I safely override Controller.OnException(ExceptionContext filterContext) like this:
protected override void OnException(ExceptionContext filterContext)
{
if(filterContext.Exception.GetType() == typeof(ArgumentException))
{
filterContext.ExceptionHandled = true;
this.InvokeHttp404(filterContext.HttpContext);
}
else
{
base.OnException(filterContext);
}
}
On the surface it seems to work, but am I storing up any problems by doing this?
Is this semantically correct thing to do?
Best way? Action method selector attribute!
To actually avoid nullable method arguments I suggest that you write an Action Method Selector attribute that will actually only match your action method when id is supplied. It won't say that argument wasn't supplied but that it couldn't match any action methods for the given request.
I would call this action selector RequireRouteValuesAttribute and would work this way:
[RequireRouteValues("id")]
public ActionResult GetAccount(int id)
{
...
}
Why is this the best solution for your problem?
If you look at your code you'd like to return a 404 on actions that match name but parameter binding failed (either because it wasn't supplied or any other reason). Your action so to speak requires particular action parameter otherwise a 404 is returned.
So when adding action selector attribute adds the requirement on the action so it has to match name (this is given by MVC) and also require particular action parameters. Whenever id is not supplied this action is not matched. If there's another action that does match is not the issue here because that particular action will get executed. The main thing is accomplished. Action doesn't match for invalid route request and a 404 is returned instead.
There's an app code for that!
Check my blog post that implements this kind of attribute that you can use out of the box. It does exactly what you're after: it won't match your action method if route data provided doesn't have all required values.
Disclaimer: this does not cover all the cases
For urls in your examples, returning 404 can be done in single line. Just add route constraint for id parameter.
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index" }, // Parameter defaults
new { id = #"\d+" } // restrict id to be required and numeric
);
And that's all. Now any matching url that has no id or id is not numeric, autimatically triggers not found error (for which there are plenty of ways to handle, one in your example, another by using custom HandleErrorAttribute, etc). And you can use non-nullable int parameters on your actions.
I managed to get this working by adding this route at the end of all routes:
routes.MapRoute("CatchAllErrors", "{*url}",
new { controller = "Error", action = "NotFound" }
);
Note: First I followed this: How can I properly handle 404 in ASP.NET MVC?

asp.net mvc 3 areas and url routing configuration

I have problem with create ulr routing for asp.net mvc3 application.
My project has this structure :
Areas
EmployeeReport
Controllers
Report
Views
Report
List
....
Controllers
Login
Viwes
Login
...
EmployeeReportAreaRegistration.cs :
public class EmployeeReportAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "EmployeeReport";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
var routes = context.Routes;
routes.MapRoute(null, "vykazy/vykazy-zamestnance", new { Area = "EmployeeReport", controller = "Report", action = "List" });
}
}
Global.asax :
routes.MapRoute(null, "prihlasit", new { controller = "Login", action = "Login" });
routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Default", action = "Welcome", id = UrlParameter.Optional });
When i try load "http://localhost/app_name/vykazy/vykazy-zamestnance
i get this exception :
The view 'List' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Report/List.aspx
~/Views/Report/List.ascx
~/Views/Shared/List.aspx
~/Views/Shared/List.ascx
~/Views/Report/List.cshtml
~/Views/Report/List.vbhtml
~/Views/Shared/List.cshtml
~/Views/Shared/List.vbhtml
Well, where I do mistake ?
Thanks
revised answer:
Adding to Context.Routes directly means it loses any information about Areas.
Either use AreaRegistration.MapRoute (which is overriden to put in the Area information).
context.MapRoute(...);
Or put the area in the DataTokens parameter (not the defaults parameter as you have done here)
context.Routes.MapRoute("", "url", new {...}, null, new {area = this.AreaName});
Your folder structure for your area should look like so:
Areas
EmployeeReport
Controllers
Views

Resources