ASP.NET MVC pass a parameter from a view to a controller - c#

I'm looking for a way to know what link was pressed when a controller was called. My link is here in my view page:
<li id="tabFiles">Files</li>
and my Files controller is here:
public ActionResult Index(string submit)
{
string s = submit;
return View();
}
I tried to pass in a string hoping it would be the id of the clicked link, but this only returns a null.

You should pass the id as query string:
<li id="tabFiles">Files</li>
public ActionResult Index(string id)
{
string s = id;
return View();
}
Or you may want to pass that in this form:
<li id="tabFiles">Files</li>
The way you send parameters to the controller depends on how you have configured routing.

You can create custom action filter and set this on controller like this:
...
[CustomActionFilter]
public class FilesController : Controller
{
...
}
and on this custom filter you can get action name like this:
public class
CustomActionFilter : ActionFilterAttribute, IActionFilter
{
void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
{
// TODO: Add your acction filter's tasks here
// Log Action Filter Call
MusicStoreEntities storeDB = new MusicStoreEntities();
ActionLog log = new ActionLog()
{
Controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
Action = filterContext.ActionDescriptor.ActionName + " (Logged By: Custom
Action Filter)",
IP = filterContext.HttpContext.Request.UserHostAddress,
DateTime = filterContext.HttpContext.Timestamp
};
storeDB.ActionLogs.Add(log);
storeDB.SaveChanges();
this.OnActionExecuting(filterContext);
}
}
More details you can find here on Microsoft site or on this question.

Related

ASP.NET MVC attribute routing not hitting correct controller

I have a couple of controllers like this:
[RoutePrefix("side-navigation")]
public class SideNavigationController : BaseController
{
[Route("{pathname}")]
public ActionResult Index(string pathname)
{
SideNavigationPopoutModel model = _sideNavFactory.Value.CreatePopout(pathname);
if (model != null)
{
return View(model);
}
return HttpNotFound();
}
}
public class CatchAllController : BaseController
{
public ActionResult Index(string pathname)
{
CatchAllModel model = _catchAllModelFactory.Value.Create(pathname);
if (model != null)
{
// TODO: Do we need this - what does it do?
// TempData.Restore(this);
return View(model);
}
return HttpNotFound();
}
}
But I cannot seem to get to my index action in the side navigation controller - if I browse to localhost/side-navigation/test it's hitting the catch all controller with side-navigation/test as it's pathname instead of the side navigation one with test as the pathname.
Can anyone see anything I am doing wrong here or how to make the side navigation controller work?
This is the route config:
// MVC attribute routing
routes.MapMvcAttributeRoutes();
// Default catch all route
routes.MapRoute(
"Default",
"{*pathname}",
new { controller = "CatchAll", action = "Index" });
Weirdly, if I change the route of the side navigation index to test/{pathname} and browse to side-navigation/test/test it will work and the controller will be hit but I don't want to add anything before the pathname
It seems that you are not using [Area] also put attribute [Route("[action]")] above method.
Ok I have fixed this by adding an asterisk before the pathname:
[RoutePrefix("side-navigation")]
public class SideNavigationController : BaseController
{
[Route("{*pathname}")]
public ActionResult Index(string pathname)
{
}
}
If anyone can explain why this works and without an asterisk doesn't, it would be greatly appreciated, as I also have a product controller set up in exactly the same way that doesn't need the asterisk
[RoutePrefix("side-navigation")]
public class SideNavigationController : BaseController
{
[Route("{*pathname}")]
public ActionResult Index(string pathname)
{
}
}

Abstraction layer between Controller and View

I am trying to create another layer between my controller and my view so that I can pass different versions of a view to a user based on their "client ID" which would be the company to which they belong.
I have the following code:
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
// set client
var client = new Client();
client.Id = Guid.NewGuid();
client.Name = "Foo";
// set user
var user = new User();
user.Id = Guid.NewGuid();
user.ClientId = client.Id;
user.Name = "Foo";
return ViewRenderer.RenderView("AddComplete", client);
}
}
My ViewRenderer class looks like this:
public static class ViewRenderer
{
public static ViewResult RenderView(string view, Guid clientId)
{
string viewName = GetViewForClient(view, clientId);
return Controller.View(view);
}
public static string GetViewForClient(string view, Guid clientId)
{
// todo: logic to return view specific to the company to which a user belongs...
}
}
The problem is, the line return Controller.View(view); in RenderView(string view, Guid clientId) gives me the error:
System.Web.Mvc.Controller.View()' is inaccessible due to its
protection level
I am interested to know how I can resolve this error or if there is a better way to do what I am trying to do, which is to display different versions of a view which are specific to the respective company to which a user belongs.
Edit: Another option I was kicking around in my head...
Is there a way to override the View() method such that I can prepend it with a directory name, for example, a user who belongs to "Acme Co." would call the same controller action as everyone else like View("MyView") but the method would actually be calling View("AcmeCo/MyView") however, I don't actually write that code in my controller, it's just derived from the user's client ID property.
You can just replace the view engine instead of adding another abstraction.
Write your own View engine (here is how to start off with a RazorViewEngine)
public class ByIdRazorViewEngine : RazorViewEngine
{
protected override IView CreateView(ControllerContext controllerContext,
string viewPath, string masterPath)
{
var id = // get something from controller context controllerContext
var newViewPath = CalculateViewPathFromId(id);
return base.CreateView(controllerContext, newViewPath, masterPath);
}
And register it in Global.asax.cs:
protected void Application_Start()
{
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new ByIdRazorViewEngine());
}
The View() method is a protected member. You can only access it from within a derived type, such as your HomeController class. Plus you're trying to access it as a static method.
You can create a base Controller that exposes your specialized view logic. For the sake of illustration, I'm going to call it DynamicViewControllerBase
public class HomeController : DynamicViewControllerBase
{
//
// GET: /Home/
public ActionResult Index()
{
// set client
var client = new Client();
client.Id = Guid.NewGuid();
client.Name = "Foo";
// set user
var user = new User();
user.Id = Guid.NewGuid();
user.ClientId = client.Id;
user.Name = "Foo";
return RenderView("AddComplete", client);
}
}
public class DynamicViewControllerBase : Controller
{
protected ViewResult RenderView(string view, Guid clientId)
{
string viewName = GetViewForClient(view, clientId);
return View(view);
}
// Unless you plan to use methods and properties within
// the instance of `Controller`, you can leave this as
// a static method.
private static string GetViewForClient(string view, Guid clientId)
{
// todo: logic to return view...
}
}
If all you want to have is the company name prefixed to your controllers, apply the RoutePrefix attribute on to your controller.
Example:
[RoutePrefix(#"{company}")]
public partial class HomeController : Controller
{
}
And in your RouteConfig file,
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Make sure this line is added
routes.MapMvcAttributeRoutes();
}
Since your users must be authenticated to sign in to their accounts, once they've authenticated them selves you can either:
Store a cookie on your users machine with the name of their company
Make calls to your database on each request to retrieve this information
Make use of ViewData[]
etc..
Once you have the name of their company, you can construct the urls with that name.
Example:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel model)
{
// ... authenticate user etc
// Redirect to
// foo.com/abc/home
return this.RedirectToAction("Index", "Home", new { company = "abc" });
}
If you're trying to work a way around this, I doubt you'll be able to as the web request first comes through a route, and the route decides which controller/action is executed, but to know the company name your action needs to execute to retrieve.

Construct url in view for Web Api with attribute routing

How can I get the url from web api in my view?
Example (from the msdn-blog):
[RoutePrefix("reviews")]
public class ReviewsController : ApiController
{
// eg.: /reviews
[Route]
public IHttpActionResult Get() { ... }
// eg.: /reviews/5
[Route("{reviewId}")]
public IHttpActionResult Show(int reviewId) { ... }
// eg.: /reviews/5/edit
[Route("{reviewId}/edit")]
public IHttpActionResult Edit(int reviewId) { ... }
}
Now I want to construct "/reviews/edit" in my view, how can I do this?
I've tried creating a little extension method, but it requires me to give every route an actual "RouteName". Is there a method I can use (like in MVC) where I can just pass the controller and action?
#Url.Action("Edit", "Reviews)
The method I'm using now (with RouteName) also doesn't allow me to use integers as parameters (unless I pass a default value). If I do need to name all my routes, how can I create a route url, but pass my parameters in the "data"-portion of my request?
Current method:
public static string ResolveWebApiRoute(this UrlHelper urlHelper, string routeName, object routeValues = null)
{
var newRouteValues = new RouteValueDictionary(routeValues);
newRouteValues.Add("httproute", true);
return urlHelper.RouteUrl(routeName, newRouteValues);
}
EDIT
When I used methods like Url.RouteUrl(new { controller = ..., action = ...}), It redirects directly to that action (e.g. new { controller = "Reviews", action = "Show"} --> /reviews/show, whilest I want it to redirect to /reviews/...
Generating links to Web API routes always require a RouteName, so you should have something like below:
[Route("{reviewId}/edit", Name="EditView")]
public IHttpActionResult Edit(int reviewId) { ... }
You can then generate a link like /reviews/1/editto Web API.
Url.RouteUrl(routeName: "EditView", routeValues: new { httpRoute = true, reviewId = 1 });
or
Url.HttpRouteUrl(routeName: "EditView", routeValues: , reviewId = 1)
Note that route names need to be specified explicitly and they are no longer generated automatically like what #Karhgath is suggesting. This was a change made from RC to RTM version.
When using route attributes I was able to get the route of a WebApi2 controller from an MVC view using something like this:
Url.HttpRouteUrl("RouteName", new { })
In WebApi2 when using AttributeRouting, route names are named by default Controller.Action, but you could specify a RouteName also:
[RoutePrefix("reviews")]
public class ReviewsController : Controller
{
// The route name is defaulted to "Reviews.Index"
[Route]
public ActionResult Index() { ... }
// The route name is "ShowReviewById"
[Route("{reviewId}"), RouteName("ShowReviewById")]
public ActionResult Show(int reviewId) { ... }
// The route name is by default "Reviews.Edit"
[Route("{reviewId}/edit")]
public ActionResult Edit(int reviewId) { ... }
Then to call it in the view you only need to set the route name and send the parameters as an anonymous object:
// Outputs: /reviews/123
#Url.Action("ShowReviewById", new { reviewId = 123 })
// Outputs: /reviews/123/edit
#Url.Action("Reviews.Edit", new { reviewId = 123 })

handling subtabs within tabs using different pages - asp .net mvc 3

I am teaching myself asp .net mvc3.
I want to create a user account page which has 3 tabs in it - say YourAddress, YourPhotos and YourProfile.
The 3rd tab (YourProfile) has 2 more subtabs in it ... ChangeDetails and DeactiaveAccount.
They are all dynamic pages and therefore I want to keep them as separate pages.
Basically the urls would be:
localhost/MyHome/YourAddress
localhost/MyHome/YourPhotos
localhost/MyHome/YourProfile/ChangePassword
localhost/MyHome/YourProfile/DeactivateAccount
(As requested I have changed the generic tab1, tab2 etc to something in real-world scenario)
I am planning to do something like this:
public class MyHomeController : Controller
{
//
// GET: /MyHome/Tab1
public ActionResult Tab1()
{
return View();
}
//
// GET: /MyHome/Tab2
public ActionResult Tab2()
{
return View();
}
//
// GET: /MyHome/Tab3
public ActionResult Tab3()
{
return View();
}
}
How do I handle the subtabs of YourProfile? How do I call a controller within a controller?
What is the best way to accomplish this.
Thanks
Have separate action method for each tab item in your controller.
public class MyHomeController : Controller
{
public ActionResult YourAddress()
{
return View();
}
public ActionResult YourPhotos()
{
return View();
}
public ActionResult YouProfile()
{
return VieW();
}
public ActionResult ChangePassword()
{
return View();
}
public ActionResult DeActivate()
{
return View();
}
}
For the sub tab content, define that route in the global.asax
routes.MapRoute("ChangePass","YourProfile/ChangePassword",
new { controller="MyHome", action="ChangePassword" });
routes.MapRoute("DeActivate","YourProfile/DeActivate",
new { controller="MyHome", action="DeActivate" });
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
Always use Url.Action Html Helper method to render a path to an action method.
<div id="tabs">
<ul>
<li>Address</li>
<li>Photos</li>
</ul>
</div>

ASP.NET MVC 3 Controller routing

So I am a little confused as to how to handle some MVC Routing
I have an AdminController
public class AdminController : Controller
{
//
// GET: /Admin/
public ActionResult Index()
{
return View();
}
public ActionResult Users()
{
return View();
}
public ActionResult Books()
{
return View();
}
}
Which works fine. So I can go to /Admin/Books
This is the admin menu for managing books. Now in there I'd like to be able to route like
/Admin/Books/ViewBook/10
or
/Admin/Books/Add
Something like that. I can't seem to grasp how to route these things that way.
I made a controller
AdminBookController
public class AdminBooksController : Controller
{
//
// GET: /AdminBooks/
public ActionResult List()
{
return View();
}
public ActionResult Add()
{
return View();
}
[HttpGet]
public ViewResult BookDetails(Guid guid)
{
return View();
}
[HttpPost]
public ViewResult BookDetails(ModifyBook Book)
{
if (ModelState.IsValid)
return View("Book successfully Edited!");
else
return View();
}
}
}
but I don't want it to be /AdminBooks I feel like /Admin/Books/Action/Param is much nicer.
Thanks in Advance!
If you want those urls to map to your AdminBooks controller, you'll need to map the following routes (in this order):
// maps /Admin/Books/ViewBook/{id} to AdminBooksController.BookDetails(id)
routes.MapRoute(
"AdminBooks_ViewBook", // Route name
"Admin/Books/ViewBook/{id}", // URL with parameters
new { controller = "AdminBooks", action = "BookDetails", id = UrlParameter.Optional } // Parameter defaults
);
// maps /Admin/Books/{action}/{id} to AdminBooksController.{Action}(id)
routes.MapRoute(
"AdminBooks_Default", // Route name
"Admin/Books/{action}/{id}", // URL with parameters
new { controller = "AdminBooks", action = "List", id = UrlParameter.Optional } // Parameter defaults
);
Note: be sure to put these mappings before the default MVC route.
Consider creating an Admin Area and adding a BookController to that Area. See the following link for a walkthrough:
http://msdn.microsoft.com/en-us/library/ee671793.aspx
You can add a new route in your Global.asax file.
See this question:
Use MVC routing to alias a controller

Categories