Url routing in mvc 3.0 - asp.net-mvc-3

I want to have below specific Url routing for my website
http://www.MyWebsite?Region=US&Area=South
based on passed Region and Area query string parameter, i want to redirect to a specific controller action.
Problems:
What should be the Url Routing ?
Whether creating a common controller action which redirects to specific action will work here?
Any help would be greatly appriciated !!
Thanks

If you want to do querystring right off the root, then you need to map routes in global this way. Inside your action you can access context and grab querystring from it. Below is my example to do twitter style usernames, but idea is the same.
routes.MapRoute("UserSettings",
"{username}/settings",
new { controller = "Home", action = "Settings", username = "" },
new { username = new NotNullOrEmptyConstraint() }
);
public class NotNullOrEmptyConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
try
{
return !String.IsNullOrEmpty(values["username"].ToString());
}
catch (Exception)
{
return false;
}
}
}
public ActionResult Settings(string username){
}

Related

Context.Request.Query["id"] return null in View

I want to get "id" parameter in View, but Context.Request.Query["id"] return null value.
Query like this:localhost:1000/MyController/Getuser/65190907-1145-7049-9baa-d68d44b1ad06
// Controller
public ActionResult Getuser(Guid id)
{
//HttpContext.Request.Query["id"] also return null
return View();
}
//in startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
I think I can get it by ViewContext.ModelState["id"].AttemptedValue
Request.Query contains query string of the request, i.e. URL part that goes after question mark: ...?param1=value1&param2=value2. URL localhost:1000/MyController/Getuser/65190907-1145-7049-9baa-d68d44b1ad06 does not contain query string. GUID 65190907-1145-7049-9baa-d68d44b1ad06 is just a part of URL path.
If for some reason you want to access id parameter from raw request, not via Model Binding, you have two options:
Pass id in query string and access it via HttpContext.Request.Query["id"]:
In this case request URL will be http://localhost:1000/MyController/Getuser?id=65190907-1145-7049-9baa-d68d44b1ad06. No changes in routes are required.
The second option is to extract id from Request.Path:
public IActionResult Getuser(Guid id)
{
var path = HttpContext.Request.Path;
var id2 = Guid.Parse(path.Value.Split('/').Last());
return View();
}

ASP.NET core HttpGet single Web API

Good Morning,
I’m having difficulty setting up my HTTPGETs and then testing the solution in Postman.
I’m trying to return a single result on both occasions however when I input the parameters nothing loads. So I'm clearly missing something which i need some help on please.
I have 1 parameter {id} in my CashMovementController and if I navigate to localhost/api/cashmovements/{id} it loads however if pass the {id} parameter in postman it fails.
Then in my BondCreditRatingsController I have 2 parameters {ISIN} & {Date} and again I'm not sure how to approach this.
Love to hear some advice/help on this please
Thanks GWS
Startup.cs
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
CashMovementsController.cs
[Route("api/[controller]")]
public class CashMovementsController : Controller
{
private ICashMovementRepository _cashmovementRepository;
[HttpGet("{id}", Name = "GetCashMovement")]
public IActionResult Get(int id)
{
CashMovement _cashmovement = _cashmovementRepository.GetSingle(u => u.CashMovementId == id);
if (_cashmovement != null)
{
CashMovementViewModel _cashmovementVM = Mapper.Map<CashMovement, CashMovementViewModel>(_cashmovement);
return new OkObjectResult(_cashmovementVM);
}
else
{
return NotFound();
}
}
}
BondCreditRatingsController.cs
[Route("api/[controller]")]
public class BondCreditRatingsController : Controller
{
private IBondCreditRatingRepository _bondcreditratingRepository;
public BondCreditRatingsController(IBondCreditRatingRepository bondcreditratingRepository)
{
_bondcreditratingRepository = bondcreditratingRepository;
}
[HttpGet("{id}", Name = "GetBondCreditRating")]
public IActionResult Get(string id, DateTime efffectivedate)
{
BondCreditRating _bondcreditrating = _bondcreditratingRepository.GetSingle(u => u.ISIN == id, u => u.EffectiveDate == efffectivedate);
if (_bondcreditrating != null)
{
BondCreditRatingViewModel _bondcreditratingVM = Mapper.Map<BondCreditRating, BondCreditRatingViewModel>(_bondcreditrating);
return new OkObjectResult(_bondcreditratingVM);
}
else
{
return NotFound();
}
}
If you want to map it to api/Controller/method/id you would need to use the code below because you want to map parameter order (no other identifier) to a specific parameter name in the action.
[HttpGet("GetCashMovement/{id}")]
Your current code should work with below since you are using named parameters and because the request can't be mapped to any other template.
/api/CashMovements/GetCashMovement?id=1
But that attribute syntax will also (possibly unintentionally) trigger:
/api/CashMovements/1
Since a sum of your defined template for that action is:
[Route("api/[controller]/{id}")]
Reason to why /api/ApiTest/GetCashMovement maps GetCashMovement.Get(int i) is because id is defined as optional in startup
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/**{id?}**");
A question mark (?) after the route parameter name defines an optional
parameter.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-3.0#create-routes

MVC Role based routing clarification

Small Question at the end of a very long explanation ...
Assuming the Admin User belonging to Admin Role and the Regular User belonging to User Role attempt to access the Index page with the following route registered in Global.asax.
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] {"tst.Controllers"}
);
In the HomeController, the Index Action Method is decorated with the Authorize attribute.
[Authorize]
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
Forcing the anonymous user to logon.
If the Admin User logs in with his/her credentials, I would like to redirect him/her to the Index Action Method in the HomeController located in the Admin area.
If a Regular user logs in, I would like to redirect him/her to the Index Action Method in the HomeController located in the User area.
I have the following code in UserAreaRegistration.cs
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"User",
"Profile/{action}",
new { area = AreaName, Controller = "Home", action = "Index" },
new { RoleConstraint = new RoleConstraint()},
new[]{ "tst.Areas.User.Controllers"}
);
}
and the following code for AdminAreaRegistration.cs
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin",
"Profile/{action}",
new { area = AreaName, Controller = "Home", action = "Index" },
new { RoleConstraint = new RoleConstraint()},
new[]{ "tst.Areas.Admin.Controllers"}
);
}
Where the RoleConstraint is defined as follows
public class RoleConstraint: IRouteConstraint
{
public bool Match(
HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection)
{
RoleProvider rp = new tst.Providers.CustomRoleProvider();
string[] roles = rp.GetRolesForUser(httpContext.User.Identity.Name);
if (roles != null && roles.Length > 0)
{
string roleName = roles[0];
string areaName = route.Defaults["area"].ToString();
return areaName == roleName;
}
return false;
}
}
The stock standard LogOn Action Method in the AdminController in the main Controllers folder...
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl)
&& returnUrl.Length > 1
&& returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//")
&& !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Question:
Am I right in thinking that, When the Admin/Regular User is validated he/she must be redirected in this line in the code snippet above
return RedirectToAction("Index", "Home");
to the appropriate Index Action Method(Read: Index Action Method in the appropriate Area).
If so, I would like to know how.
I am confused because a constant string "Profile" is involved and it is not the usual stuff involving an action method and a controller name. "Profile" is neither a controller nor an action method.
Inspired by this post
MVC role-based routing
Instead of
return RedirectToAction("Index", "Home");
in the LogOn Action Method, I replaced it with
return Redirect("/Profile");
It worked !!!
However, what I don't understand is, when I click Log Off, it renders the Index page in the main Views folder. So I have to click LogOff again to be taken back to the LogOn page.

Passing subdomain info to controller in IRouteConstraint.Match

In subdomain route constraint I want to pass subdomain name to controller with the code below. But in controller subdomain entry is missing (only action and controller entries exist). What is wrong here? Or should I sniff subdomain in controller itself (with the help of Request.Headers["HOST"])? What is the best practice?
public class SubdomainRouting : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
//
//get subdomain here
//
if (values.ContainsKey("subdomain") == false)
{
values.Add("subdomain", subdomain);
}
return true;
}
}
I think you are interpreting the values argument the wrong way. According the MSDN, it is An object that contains the parameters for the URL. So I'm not sure if its even the same RouteDataDictionary you get in the controller.
The easiest way to get the subdomain would be from the Request.Url.Host you get in the controller.
Well I changed my routing constraint implementation. Instead of implementing IRouteConstraint, I implemented RouteBase. This way I was able to add route values:
public class SubdomainRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData returnValue = null;
//some code here
returnValue = new RouteData(this, new MvcRouteHandler());
returnValue.Values.Add("controller", "SomeController");
returnValue.Values.Add("action", "SomeAction");
returnValue.Values.Add("key", some_value_to_pass_to_controller);
//some code here
return returnValue;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}

RouteHandler vs ControllerFactory

new to asp.net mvc (using v3 + razor) and am wondering how to best solve a problem with creating dynamic routes based on a database. Essentially, the main site navigation will be entered into a database and I want to load them up as routes. i.e. - Load Category list from database, then append the routes to the routing engine if possible...
mysite.com/cars
mysite.com/televisions
mysite.com/computers
etc....
Each category after the slash comes from the db, but, there are regular entries like /about and /contactus that will not be in the database and have been statically entered in the global.asax... my question is:
For the dynamic database URLs should I use a custom RouteHandler or pehaps create a ControllerFactory that will match and handle the requests for the entries loaded from the database. Is it possible to have the DefaultControllerFactory handle the routing if my RouteHandler or CustomControllerFactory don't find the route in the list from the database? Thanks for any help, very first project with this so I'm not sure what the best route is ;) no pun intended...
Update:
Tried using a route constraint that pulls from the database but it conflicts with the default route now... here is my custom constraint and routes:
public class CategoryListConstraint : IRouteConstraint
{
public CategoryListConstraint()
{
var repo = new Repository<Topic>();
var cats = repo.All();
var values = new List<string>();
foreach (var c in cats)
{
values.Add(c.URI.Replace("/", "").Replace("?", ""));
}
this._values = values.ToArray<string>();
}
private string[] _values;
public bool Match(HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection)
{
// Get the value called "parameterName" from the
// RouteValueDictionary called "value"
string value = values[parameterName].ToString();
// Return true is the list of allowed values contains
// this value.
return _values.Contains(value);
}
}
and here are the routes:
Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Categories",
"{category}/{*values}",
new { controller = "Category", action = "List" },
new CategoryListConstraint()
);
Routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
The home page www.mysite.com loads using the Default route. All the URLs that match the constraint list are loaded by the category route... but if I have the www.mysite.com/admin or www.mysite.com/aboutus these are getting picked up by the Categories route even though the values are not in the constraint list. Confused...
What about something like this?
Categories controller:
public ActionResult List(string category)
{
var products = _repo.Get(category); // however you are getting your data
return View(products);
}
Routes
routers.MapRoute(
"About",
"About",
new { controller = "Home", action = "About" });
//... other static routes
routes.MapRoute(
"CategoriesList",
"{id}",
new { controller = "Categories", action = "List" },
new { id = #"\w+" });
The incoming URL is tested against each Route rule to see if it matches - and if a Route rule matches then that rule (and its associated RouteHandler) is the one that is used to process the request (and all subsequent rules are ignored). This means that you want to typically structure your routing Rules in a "most specific to least specific" order
source
Found the exact solution I was looking for. Code is below. I managed to avoid using Controller Factories or implementing a custom IRouteHandler by using extending the RouteBase class which worked perfectly and allows me to pass control down to the default mvc route is something specific isn't hit. BTW - constraints ended up not working properly as the broke the controllers associated with the default route (although the default route was getting hit)
public class CustomRoutingEngine : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeHandler = new MvcRouteHandler();
var currentRoute = new Route("{controller}/{*URI}", routeHandler);
var routeData = new RouteData(currentRoute, routeHandler);
// implement caching here
var list = GetConstraintList();
// set your values dynamically here
routeData.Values["controller"] = "Category";
// or
routeData.Values.Add("action", "List");
// return the route, or null to have it passed to the next routing engine in the list
var url = Util.StripSlashOnFrontAndBack(httpContext.Request.Path.ToLower()).Split('/')[0];
if (list.Contains(url))
return routeData;
return null; // have another route handle the routing
}
protected List<string> GetConstraintList()
{
using (var repo = new RavenRepository<Topic>())
{
var tops = repo.Query().Where(x => x.Hidden == false).ToList()
.Select(x=>x.Name.ToLower());
List<string> list = new List<string>();
list.AddRange(tops);
repo.Dispose();
return list ?? new List<string>();
}
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
//implement this to return url's for routes, or null to just pass it on
return null;
}
}
Then my register routes method looks like so:
Routes.Clear();
// Set Defaults
Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
AreaRegistration.RegisterAllAreas();
routes.Add(new App.Helpers.CustomRoutingEngine());
Routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);

Resources