Attribute Routing : Object reference not set to an instance of an object - asp.net-mvc

I'm using this codes to Attribute Routing .
my controller :
[RouteArea("Administrator")]
[Route("{action}")]
public partial class HomeController : Controller
{
[HttpGet]
[Route("~/Home/Template/{id}")]
public virtual ActionResult Template(string template)
{
switch (template.ToLower())// error :Object reference not set to an instance of an object..
{
case "main":
return PartialView(Url.Content(MVC.Administrator.Home.Views.Main));
default:
throw new Exception("template not known");
}
}
}
RouteConfig :
internal static class RouteConfig
{
internal static void RegisterRoutes(AreaRegistrationContext context)
{
context.MapRoute(
"Administrator_default",
"Administrator/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { string.Format("{0}.Controllers", typeof(RouteConfig).Namespace) });
}
}
url : http://localhost:22738/home/template/main
how to use Attribute Routing ?
Is there a way to solve the problem? I have no idea

It would behoove you spend some time reading the documenation for attribute routing. See: http://blogs.msdn.com/b/webdev/archive/2013/10/17/attribute-routing-in-asp-net-mvc-5.aspx
First, you haven't enabled attribute routing. You need the following in RouteConfig.cs:
routes.MapMvcAttributeRoutes();
Second, you can't apply [Route] to a controller class. You can use [RoutePrefix] if that's what you're looking for. However, bear in mind, that if you include a param in your route prefix, all your action must accept that param. Although, since you have a param of action here, it's entirely possible that you just don't understand how this works. You don't pass the action name with attribute routing. The action that is hit is determined by the one that has the matching route.
Third, when you define a route on an action, you need only specify the portion of the route not covered by RouteArea or RoutePrefix. Using the tilde (~) says that you want to ignore all set prefixes and define the whole route for the action, if that's what you actually want here, that's fine, but just keep in mind that you don't have to follow the /Controller/Action/{id} convention employed by the default route in RouteConfig.cs. The whole point of attribute routing is to define custom routes easily. If you're going to rely on the default route, you might as well just use that and forget about attribute routing.
Finally, in your route, you're accepting the param, id, but your action doesn't take it. Instead it has its own param of template. The params need to match or either the route will not match or the action won't be able to work.
To summarize, the following is likely what you're looking for:
[RouteArea("Administrator")]
[RoutePrefix("home")]
public partial class HomeController : Controller
{
[Route("template/{template}")]
public virtual ActionResult Template(string template)
{
switch (template.ToLower())// error :Object reference not set to an instance of an object..
{
case "main":
return PartialView(Url.Content(MVC.Administrator.Home.Views.Main));
default:
throw new Exception("template not known");
}
}
}

Related

Simple default operation with optional parameters

I'm trying to create a method that takes two strings and gets called if I provide the params or not. It's proving to be difficult for me.
Here is my controller with the test method:
public class TestController : ApiController
{
[HttpGet]
public string Get(string one, string two )
{
return "Testing";
}
}
I would like the method to be available at the folloring urls:
url/test/
url/test/
url/test/?one=1&two=2
url/test/?one=1
url/test/?two=2
url/test/get
url/test/get?one=1&two=2
url/test/get?one=1
url/test/get?two=2
I'm also planning to add POST support, but I think this will prove to much for WebAPI and I'll have to have a dedicated model class.
My routes are:
.Routes.MapHttpRoute("Basic", "{controller}/{action}",
defaults: new { action = "Get" });
I understand that I could set up routes to specify the 'one' and 'two' parameters, but my routes definitions sit in a central place and I'm not ready to pollute my global routes to cater for a special case in a controller (and then do it for hundred other methods).
It seems to me like a pretty basic scenario - what is the pattern to follow here?
You need to set default values on the parameters to make them optional:
[HttpGet]
public string Get(string one = null, string two = null )
{
return "Testing";
}

Why do you need a route defined for Html.Action?

I have created an otherwise empty ASP.NET MVC 3 application with 2 controllers, HomeController and OtherController.
HomeController.cs looks like this:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
Index.cshtml looks like this:
#Html.Action("Index", "Other")
And, of course, Othercontroller.cs:
public class OtherController : Controller
{
[ChildActionOnly]
public ActionResult Index()
{
return Content("OK!");
}
}
So far, so good. I run the app, and it tells me everything is OK!
Now, I take the default RegisterRoutes from Global.asax.cs:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
And I crumple it up, so that no routes match OtherController:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("Default", "", new { controller = "Home", action = "Index" });
}
Now, when I run the page, it crashes with this error message:
System.InvalidOperationException: No route in the route table matches the supplied values.
Source Error:
Line 1: #Html.Action("Index", "Other")
I specified the controller name and action name in the call to .Action. No URLs are being generated, and no requests are being made. Why does routing even need to get involved?
I think that this blog post will help you understand a little more:
http://blogs.charteris.com/blogs/gopalk/archive/2009/01/20/how-does-asp-net-mvc-work.aspx.
Essentially, routing is involved in determining which controller to 'fire up' to handle the request and appropriate action to invoke based on the parameters that you sent and the MVCRouteHandler uses those to make a decision. Just because you tell it which controller in your action does not make it magically ignore the routing table, go straight to that controller class and bypass all the other MVC goodness that happens in the back-end. Remember, these #HTML.Action methods can take a whole lot of overloads which could influence which route in the routing table to use (think URL structure for instance).
The MVC paths are not to static content and as such, have to be parsed through the URLRoutingModule which uses the routing table to decide on what to do. Since you have no matching route - you get an error.
EDIT
In my diatribe, I did not actually address your final statement. You're right, no URL was generated, but a request to the application was generated. HTML.Action still will use routing to determine what controller, action, area, parameters to use. I think it be fair to say in simple terms that it's like generating an ActionLink and clicking it for you.
Routing got involved by using Html.Action on a controller. When the engine couldn't find the "Other" HtmlHelper with an action of "Index" it defaulted to executing that path which means passing through routing. No route matched so it threw an error.
InvalidOperationException The required virtual path data cannot be found.
http://msdn.microsoft.com/en-us/library/ee721266.aspx
The solution for me was as follows:
I had the following line that was giving me an error:
#{Html.RenderAction("ActionName", "ControllerName");}
Adding a thrid parameter solved the error:
#{Html.RenderAction("ActionName", "ControllerName", new { area = string.Empty });}

Value cannot be null. Parameter name: String in asp.net mvc C#

I am pretty new to asp.net mvc. I want to get the parameter string in my url
http://localhost/Item/ItemSpec/3431?dep=62&cat=129&tab=2
How can I get the value=3431?
I tried to used HttpContext.Current.Request.QueryString["id"], but it's not work. 3431 is the id of the item that display in my page, that's why I used ["id"].
Thanks you so much.
The 3431 is part of the path of the request, not part of the query string. You could use HttpRequest.Path to get at the path, but MVC routing should allow you to simply write a controller method which accepts the ID as a parameter. I suggest you read up on how to configure routing. (Just searching for ASP.NET routing or MVC routing will give you lots of articles.)
Assuming the default route is configured in Global.asax ({controller}/{action}/{id}) you could have your controller action take an id parameter and the default model binder will automatically set its value:
public ActionResult Foo(string id)
{
...
}
If you want to fetch this id value from some other portion of your code that does have access to an HttpContext you need to fetch it from the RouteData:
var id = HttpContext.Request.RequestContext.RouteData["id"];
RouteData is available in all standard MVC locations. In your example you have used the static HttpContext.Current property which is something that you should never use. I suspect that you are trying to fetch this id from a portion of your code where you are not supposed to have access to the HttpContext. So you'd better fetch this id using standard techniques and then pass it as parameter to other parts of your code.
If Item is your controller, and ItemSpec is action, you can get the Id just by
public ActionResult ItemSpec(int id) { }
You routing have to be setup to:
context.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index" }
);
If you haven't changed your routing so it's still defined as it was when you created Asp.net MVC Web project then this should be one of your controllers:
public class ItemController : ControllerBase
{
...
public ActionResult ItemSpec(int id, int dep, int cat, int tab)
{
// implementation that uses all four values
}
...
}
This is of course just one of the actions in it. There may be others as well. Most likely the Index one that's generated by default and is also used by default routing...

Removing direct reference to URL in view when two different URLs map to single controller action?

Basically I'm looking for the best way to remove the need to hard code any actual URLs within views when two different URLs call the same controller action. As shown below, two routes define different URLs, one for creating a new customer, whilst the other is for editing an existing customer. Inside the controller action it determines what to do by checking the value of AccountNumber - if it was not provided (null) then it knows that it is creating a new customer.
routes.MapRoute(
"Customer_Edit",
"Customer/Edit/{CustNum}",
new { controller = "Customer", action = "Edit", AccountNumber = "{CustNum}" }
);
routes.MapRoute(
"Customer_Create",
"Customer/Create",
new { controller = "Customer", action = "Edit" }
);
The downside to this approach is that you can't use Url.Action(...) in the views to determine the URL because there are two different URLs mapping to the controller action. I had hoped that the call to Url.Action(...) would determine which to use by the "RouteValues" provided, but it just appears to use the 1st matching route defined. So in this case if you call
Url.Action("Edit", "Customer", new { CustNum = 2 })
you get "Customer/Edit/2" as you'd expect, but if you call
Url.Action("Edit", "Customer")
the view outputs the URL "Customer/Edit/{CustNum}".
Is there anyway using this configuration that you would not need to hard code URLs into the view? Or is it considered better practise to have a separate action for creating new customers and another action for editing customers, so you end up with the following
public class CustomerController : Controller
{
public ActionResult Create()
{ return CreateOrEdit(null); }
public ActionResult Edit(int custNum)
{ return CreateOrEdit(custNum); }
private ActionResult CreateOrEdit(int? custNum)
{....}
}
Use Url.RouteUrl instead and specify the route you want. In addition to actually working, RouteUrl is on the order of 10* faster than Action.

asp.net mvc - dynamic controller based on authenticated user

If I want the default url of my web app to display completely different UIs depending on the user, what is the best way to accomplish this? I don't really want to use the same controller for every type of user. To put it another way, if a user is logged in and goes to http://mysweetapp.com and is an admin user, they should get what they would see the same thing as if they had gone to http://mysweetapp.com/admin. If the user is logged in as a normal user, they should see the same thing as if they had gone to http://mysweetapp.com/normaluser
Should I just make a "redirect" controller as my default and have it send the client to the appropriate controller?
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Redirect", action = "Index", id = "0" });
I've also tried creating my own ControllerFactory, but I don't think I was clear on the concept and couldn't get it to work.
Thanks
The cleanest way in my opinion would be to create a custom route handler to be used by your default route. Then you can separate out which controller to be used if the controller name is your default controller name, in the example below, it is: Home. Then check if the user is an administrator or not and process the request with the controller you would like to use.
Here is the code:
public class CustomHttpHandler : IHttpHandler
{
public RequestContext RequestContext { get; private set; }
public CustomHttpHandler(RequestContext requestContext)
{
try
{
string controllerName = RequestContext.RouteData.GetRequiredString("controller");
if (controllerName.Equals("home", StringComparison.CurrentCultureIgnoreCase))
{
bool isAdmin = RequestContext.HttpContext.User.IsInRole("Admin");
controllerName = isAdmin ? "admin" : "normaluser";
}
IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
IController controller = factory.CreateController(RequestContext, controllerName);
if (controller != null)
{
controller.Execute(RequestContext);
}
}
finally
{
factory.ReleaseController(controller);
}
}
}
public class CustomRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new CustomHttpHandler(requestContext);
}
}
// Now use the CustomRouteHandler when you map your default route.
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
).RouteHandler = new CustomRouteHandler();
Hope this helps.
For simplicity, in your HomeController Index method (or whatever default controller you are using) you could put some code like this and then the links from the AdminIndex view or the Index view can send the users to appropriate areas when they start navigating round your site - that way you have one shared controller and the other controllers can be specific to the user type.
return user.IsAdministrator ? View("AdminIndex") : View("Index");
the user.IsAdministrator call is pseudocode of course - replace this with whatever method you are using to work out if the user is an admin user
If you don't want to use the same controller set up individual controllers and views for each item first - mysweetapp.com/admin and mysweetapp.com/normaluser.
You can then redirect specific users to this page through a default controller based on their logged in role.
if (User.IsInRole("Admin")
{
return RedirectToAction("Index", "admin");
}
else if (User.IsInRole("Standard")
{
return RedirectToAction("Index", "normaluser");
}
What you might want to consider is areas. This would allow you to have separate controllers for each area. Then permit access to those areas based on roles or whatever you wish.
This will give you routes like '/admin/controller/action', '/users/controller/action', etc. The 'pattern' separates all your controllers by namespace, and handles the routing quite well. Separate master pages easily, etc.
It won't give you the (potentially confusing, IMO) '/' and '/admin/' looking the same to an admin user, but it will let you separate the content and controllers.
What you are describing would lead to potentially tons of methods for each controller, something that is generally frowned upon by the MVC/REST crowd. It's not horrible, but its not considered best practice either.
You can read about areas at this blog here. Google 'asp.net mvc areas' for more.
--------edit-----------
To expand a bit:
Without custom routes or some other shenanigans, actions are mapped to controllers by the url. So if you want to keep all admin actions and views different, but on the root url, along with normal user actions, this would lead to one big controller that has to handle all these actions, or some strange "if this role, this view; if that role, that view" sort of nonsense that would have to happen in each action. Kind of a mess to debug potentially.
Similarly, the default view engine finds the views based on the url as well.
This would mean that all of your views are going to sit in one big ugly directory full of all sorts of weird similarly named but differently behaving views.
In short, this would become a potentially horrific maintenance nightmare, depending on complexity of the application.
Could you create a class that extends from DefaultControllerFactory and overrides CreateController?
public class RedirectControllerFactory : DefaultControllerFactory
{
public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
if (controllerName.Equals("Redirect"))
{
controllerName = requestContext.HttpContext.User.IsInRole("Admin") ? "Admin" : "NormalUser";
}
return base.CreateController(requestContext, controllerName);
}
}
Then in your Application_Start():
protected void Application_Start()
{
// ...
ControllerBuilder.Current.SetControllerFactory(new RedirectControllerFactory());
}

Resources