How to await a service call in routes.MapRoute in MVC5 - c#

I am doing a massive overhaul on a custom CMS that I am writing in MVC5. This custom CMS has "pages" that have "urls" stored in a database. For instance if a user requested /stackoverflow in the browser and there was a page in the database with /stackoverflow listed as the url, then I am serving the database content at the specificed CoreCms/Index Controller and view with the database content as a model property. The idea is that I can use this single controller/view to serve up any page in the database.
The rewrite that I am working uses dependency injection and async calls only in the service layer. It seems that I am having a bit of trouble with the RouteConfig RegisterRoutes static method.
Here's some code...
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("FileBrowser/{pathInfo}");
routes.MapRoute(
name: "CmsRoutes",
url: "{*permalink}",
defaults: new { controller = "CmsCorePage", action = "Index" },
constraints: new { url = new CmsCoreRouting() }
);
routes.MapRoute(
name: "ArticlesCategoryRoute",
url: "Articles/{categoryURL}",
defaults: new { controller = "CmsCoreArticles", action = "Index", categoryURL = UrlParameter.Optional }
);
routes.MapRoute(
name: "ArticlesPostsRoute",
url: "Articles/{categoryURL}/{postURL}",
defaults: new { controller = "CmsCoreArticles", action = "ArticlePost", categoryURL = UrlParameter.Optional, postURL = UrlParameter.Optional }
);
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Note that I am rewriting old code and the constraints: new { url = new CmsCoreRouting() } is code that I intend on altering here I believe.
For reference, here is the CmsCoreRouting class...
public class CmsCoreRouting : IRouteConstraint
{
private ICoreCmsServices _coreSvc;
public CmsCoreRouting()
{
}
public CmsCoreRouting(ICoreCmsServices coreSvc)
{
_coreSvc = coreSvc;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if (httpContext?.Request?.FilePath == null) { return false; }
string myURL = httpContext.Request.FilePath;
if (myURL.StartsWith("/"))
{
myURL = myURL.Substring(1, myURL.Length - 1);
}
myURL = myURL.ToLower();
var siteId = CoreCms.Core.Settings.CoreCmsSettings.SiteId;
var cmsPage = AsyncUtility.RunSync(() => _coreSvc.PageService.FindBySiteAndUrlAsync(siteId, myURL));
if (cmsPage != null)
{
return true;
}
var cmsArticle = AsyncUtility.RunSync(() => _coreSvc.ArticleService.FindCategoryBySiteAndUrlAsync(siteId, myURL));
if (cmsArticle != null)
{
return true;
}
return false;
}
}
Problem: When MVC starts, it calls the parameterless constructor on CmsCoreRouting (because I told it to in url = new CmsCoreRouting()) but I am not sure how to both use AutoFac's DI so that I don't have to pass around my own instances of the service and repository and DbContext on this RegisterRoutes function.
Any help on this would be great. I would like to do this "right".

Just ask the resolver (AutoFac) to create it for you:
routes.MapRoute(
name: "CmsRoutes",
url: "{*permalink}",
defaults: new { controller = "CmsCorePage", action = "Index" },
constraints: new { url = DependencyResolver.Current.GetService<CmsCoreRouting>() }
);

Related

ASP.net customized routing with Action/Id

I've been trying to change the routing for my website for the past few hours and I just can't find out what ASP.net wants from me!
This is my default routing:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{Id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional, }
);
It successfully brings up Home/Index whenever I go to these URLs:
/
/Home
/Home/Index
However, I have an action called "K" like below which receives a parameter "name":
public ActionResult K(string name)
I want to define a route that will redirect me to /Home/K/{name} with this template:
website.com/K/{name}
I tried this route below but It doesn't work:
routes.MapRoute(
"OnlyK",
"K/{id}",
new { controller = "Home", action = "K", id = UrlParameter.Optional }
);
Also even without this route config, if I go to website.com/Home/K/something It will not recognize "something" as id (controller parameter == null)!
What am I doing wrong?
The url website.com/Home/K/something results in a null value for argument name in the K action method ActionResult K(string name) because the argument names don't match.
This method is being served via the the route Default, which
declares a parameter with name id (via {controller}/{action}/{id}), whereas action method K has an argument named name.
You can solve this by overruling the argument name on the action method, via the BindAttribute so that both match.
ActionResult K([Bind(Prefix ="id")] string name)
To do a redirect from website.com/K/{name} to website.com/Home/K/{name} you can set up a custom IRouteHandler that takes care of this redirect.
Register a route that handles any request that match route K/{id} and redirect these to a route with a wellknown name (here: K).
Because of the argument name mismatch discussed about we'll use {id} instead of {name} in our routes.
routes.Add(new Route("K/{id}", new RedirectRouteHandler("K")));
Define this route K as below.
routes.MapRoute(
name: "K",
url: "Home/K/{id}",
defaults: new { controller = "Home", action = "K", id = UrlParameter.Optional }
);
The RouteHandler and RedirectHandler doing the redirect.
The HttpResponse class has a RedirectToRoute method that can deal with route names and route values, without having to construct urls ourselves.
class RedirectRouteHandler : IRouteHandler
{
private readonly string _routeName;
public RedirectRouteHandler(string routeName)
{
_routeName = routeName;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new RedirectHandler(this._routeName, requestContext.RouteData.Values);
}
}
class RedirectHandler : IHttpHandler
{
private readonly string _routeName;
private readonly RouteValueDictionary _routeValues;
public RedirectHandler(string routeName, RouteValueDictionary routeValues)
{
this._routeName = routeName;
this._routeValues = routeValues;
}
public bool IsReusable { return false; }
public void ProcessRequest(HttpContext context)
{
context.Response.RedirectToRoute(this._routeName, this._routeValues);
}
}
Note that the order of the registered routes matters; RegisterRoutes looks like.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new Route("K/{id}", new RedirectRouteHandler("K")));
routes.MapRoute(
name: "K",
url: "Home/K/{id}",
defaults: new { controller = "Home", action = "K", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}

Asp.Net MVC 5 route not getting hit

I have this in my RouteConfig:
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: "StoreSearch",
url: "{storeName}",
defaults: new { controller = "Search", action = "Store" }
);
Basically I have a 2nd route so if I type in localhost:9000/facebook there is no such controller by the name of facebook so my 2nd router should pickup facebook as the storeName and hit my Search controller's Store action. But at the moment I get 404.
Any ideas how do I fix this?
If you have specific set of stores or a way to verify them you can add a constraint to the route.
To add a generic constraint with match predicate
public class ServerRouteConstraint : IRouteConstraint
{
private readonly Func<Uri, bool> _predicate;
public ServerRouteConstraint(Func<Uri, bool> predicate)
{
this._predicate = predicate;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName,
RouteValueDictionary values, RouteDirection routeDirection)
{
return this._predicate(httpContext.Request.Url);
}
}
and then in the route add a paramater
routes.MapRoute(
name: "StoreSearch",
url: "{storeName}",
defaults: new { controller = "Search", action = "Store" }, constraints:
//this could be new { controller ="pattern" }
new {
serverRoute = new ServerRouteConstraint(url =>
{
//this will check that the route starts with a specific string in this case Settings
return url.PathAndQuery.StartsWith("/Settings",
StringComparison.InvariantCultureIgnoreCase);
})
});
For examples check: https://www.c-sharpcorner.com/UploadFile/dacca2/route-constraints-in-mvc/
Also, routes should be added from most specific to most general.
You should place any custom route above the Default route.
Just swap the order and you good to go
//before default route
routes.MapRoute(
name: "StoreSearch",
url: "{storeName}",
defaults: new { controller = "Search", action = "Store" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

Controller Redirect not executing method

Following all suggested remedies, still my method is not getting called.
The method being called is in the same controller. TEST can be called either by itself through a link on a separate page, or when a user selects a saved "TEST" view that they wish to load again. In this case, the below LoadUserSelectedTransaction is called.
I've tried specifying the controller to use with no luck, hard coding requestType, [HTTPPOST] attribute, removing FormMethod.Post from the view
View is as follows:
using (Html.BeginForm("LoadUserSelectedTransaction", "TransactionList", FormMethod.Post))
Controller is as follows
public class TransactionListController : Controller
{
public static bool userLoaded = false;
public static string userFriendlyName;
public static string transaction;
public static string requestType;
//[HttpPost]
public ActionResult LoadUserSelectedTransaction(FormCollection formdata)
{
userLoaded = true;
userFriendlyName = formdata["UserLoadedTransaction"];
var model = new MyTransactionsDBHandle();
transaction = model.ReturnUserLoadedTransaction(userFriendlyName, User.Identity.Name);
var myTransaction = new MyTransactionsModel();
requestType = myTransaction.ReturnRequestType(transaction);
if (requestType == "")
{
requestType = formdata["CurrentTransactionPage"];
}
// need to check for duplicate transactions types, such as CC_SALE also being a POS Surcharge transaction
// CC_SALE has EMV, ship to, bill to etc, POS Surcharge does not
// CC_VOID with and without card - With card will have NGT_TRACKDATA tag
// DB_SALE with and without cashback - Cashback will have NGT_DC_CASH_BACK_AMOUNT tag
if (requestType == "DB_SALE")
{
if (transaction.Contains("NGT_DC_CASH_BACK_AMOUNT"))
return RedirectToAction("DB_Sale_With_Cash_Back");
return RedirectToAction("DB_Sale_No_Cash_Back");
}
else if (requestType == "CC_SALE")
{
if (transaction.Contains("NGT_EMV_DATA"))
return RedirectToAction(requestType);
return RedirectToAction("CC_POS_Surcharge");
}
else if (requestType == "CC_VOID")
{
if (transaction.Contains("NGT_TRACKDATA"))
return RedirectToAction(requestType);
return RedirectToAction("CC_Void_No_Card");
}
else
return RedirectToAction(requestType);
}
}
Method being called:
public ActionResult TEST(FormCollection formdata)
{
ViewModel model = new ViewModel();
if (userLoaded) // static in same controller
{
userLoaded = false;
if (requestType == "My Transactions")
{
var currentTransactionPage = formdata["CurrentTransactionPage"];
return RedirectToAction(currentTransactionPage);
}
model.myTransactions.ParseUserTransactionString(transaction, model);
}
return View(model);
}
Any suggestions are greatly appreciated, thanks in advance!
RouteConfig.cs
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 = "User", action = "Login", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "ClientTablePages",
url: "{controller}/{action}/{pageNumber}",
defaults: new { controller = "Client", action = "Index", page = UrlParameter.Optional }
);
routes.MapRoute(
name: "DivisionTablePages",
url: "{controller}/{action}/{pageNumber}",
defaults: new { controller = "Division", action = "Index", page = UrlParameter.Optional }
);
routes.MapRoute(
name: "MerchantTablePages",
url: "{controller}/{action}/{pageNumber}",
defaults: new { controller = "Merchant", action = "Index", page = UrlParameter.Optional }
);
routes.MapRoute(
name: "TerminalTablePages",
url: "{controller}/{action}/{pageNumber}",
defaults: new { controller = "Terminal", action = "Index", page = UrlParameter.Optional }
);
routes.MapRoute(
name: "ResetPassword",
url: "{controller}/{action}/{id}",
defaults: new { Controller = "User", action = "UserResetPassword", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "MyTransactions",
url: "{controller}/{action}/{pageNumber}",
defaults: new { Controller = "MyTransactions", action = "MyProfile", page = UrlParameter.Optional }
);
}
}
RedirectToAction returns a 302 redirect to the browser which is always a GET request.
In comparison Server.TransferRequest(url, true) performs a server redirect preserving the postBack values using a POST request.
P.S. Browser won't be aware of this redirect hence the browser url will not reflect the new address

Modifying the routing asp mvc

In my ASP MVC application, I need to show the URL path like this:
domain.com/viewName
I have the route config in the follow way:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.RouteExistingFiles = false;
routes.IgnoreRoute("{*allfiles}", new { allfiles = #".*\.(css|js|gif|jpg|png)" });
routes.MapRoute(
"Theme",
"theme/{theme}/{page}",
new { controller = "Theme", action = "Index", page = UrlParameter.Optional },
new[] { "Tenant.Controllers" }
);
routes.MapRoute(
"ThemeSlider",
"theme/slider/{id}",
new { controller = "Theme", action = "Slider" },
new[] { "Tenant.Controllers" }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { "Tenant.Controllers" }
);
routes.MapRoute(
"Default_tenant",
"{page}",
new { controller = "Home", action = "Index", page = UrlParameter.Optional },
new[] { "Tenant.Controllers" }
);
routes.MapMvcAttributeRoutes();
}
But it does not works, I have to specify the page parameter in the follow way: domain.com/?page=viewName. How should be the route config that leaves the follow routes:
domain.com.co/controller/action/
domain.com.co/view
Any sugestion ?? Thanks in advance
You mean to get rid of the controller? This sort of thing will work, but beware of conflicts between routes in different controllers:
routes.MapRoute(
name: "HomeRoute",
url: "{action}",
defaults: new {controller = "Home", action = "Index"},
constraints: new { homeControllerConstraint = new HomeControllerConstraint() }
);
Now every action in the home controller maps to Domain.com/Action instead of Domain.com/Home/Action.
Here is some reflective code to efficiently work out whether a route is in the home controller, for instance.
internal class HomeControllerConstraint : IRouteConstraint
{
private static readonly Lazy<HashSet<string>> homeMethods = new Lazy<HashSet<string>>(CreateHomeMethods);
private static HashSet<string> CreateHomeMethods()
{
return typeof(HomeController).GetMethods().Select(e => e.Name.ToLower()).ToSet();
}
public static bool Exists(string name)
{
return homeMethods.Value.Contains(name.Trim().ToLower());
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return homeMethods.Value.Contains(values["action"].ToString().ToLower());
}
}

MVC custom routing with validation

Masters,
In my project I have specified many different routes something like follows.
routes.MapRoute(
name: "StudentMerit",
url: "Student-Merit",
defaults: new { controller = "StudentMerit", action = "GetData"});
routes.MapRoute(
name: "StudentResults",
url: "Student-Results",
defaults: new { controller = "StudentResults", action = "GetData"});
As you can see url is hyphen separated and i am not much worried about action name
All this routes can be rewritten if i can tell MVC that Replace Hyphen with nothing in
URL and it will be my controller.
Something like,
routes.MapRoute(
name: "AllInOne",
url: "{Cont-roller}",
defaults: new { controller = {Cont-roller}.replace("-",""), action = "GetData"});
Is there any way to do this?
Please help.
Thanks in advance.
Create your own RouteHandler. I don't know if this is the best solution though.
public class RemoveDashRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.RouteData.Values["controller"] = ((string)requestContext.RouteData.Values["controller"]).Replace("-", String.Empty);
return base.GetHttpHandler(requestContext);
}
}
Usage
routes.MapRoute(
name: "AllInOne",
url: "{controller}",
defaults: new { controller = "Default", action = "GetData" }
).RouteHandler = new RemoveDashRouteHandler();
Edit for alternative solution
I found a better solution (in my opinion) by sub-classing Route then overriding GetRouteData. It's better since Route's responsibility is to generate the RouteData while MvcRouteHandler's responsibility is to get the IHttpHandler.
public class RemoveDashRoute : Route
{
private const string ControllerKey = "controller";
public RemoveDashRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints = null, RouteValueDictionary dataTokens = null, IRouteHandler routeHandler = null)
: base(url, defaults, constraints ?? new RouteValueDictionary(), dataTokens ?? new RouteValueDictionary(), routeHandler ?? new MvcRouteHandler())
{
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeData = base.GetRouteData(httpContext);
if (routeData != null && routeData.Values.ContainsKey(ControllerKey))
{
routeData.Values[ControllerKey] = ((string)routeData.Values[ControllerKey]).Replace("-", String.Empty);
}
return routeData;
}
}
Usage
routes.Add("AllInOne", new RemoveDashRoute(
url: "{controller}",
defaults: new RouteValueDictionary(new { controller = "Home", action = "GetData" }))
);

Categories