I am trying to create a plugin which will override the TopicsDetails.cshtml page. I added a route like this:
routes.MapRoute("Nop.Plugin.Other.CustomTopic.ViewCustomTopic", "{SeName}",
new { controller = "CustomTopic", action = "TopicDetails", SeName = UrlParameter.Optional },
new[] { "Nop.Plugin.Other.CustomTopic.Controllers" });
This is getting all the {SeName} to my CustomTopicController .Even the products SeName.
If I add this instead of the older one:
routes.MapRoute("Nop.Plugin.Other.CustomTopic.ViewCustomTopic",
new { controller = "CustomTopic", action = "TopicDetails" },
new[] { "Nop.Plugin.Other.CustomTopic.Controllers" });
I get an error because the TopicDetails(int itemId) Action receives an integer which is not provided as we know that GenericPathRoutes.cs Provides that integer.
How can I override the Rules of GenericPathRoutes.cs to do it so that only the topic SeName would hit my Controller or is there other way to do that kind of work or is it even possible to do?
Recently i wrote an article which shows how to override localized route and i used the TopicDetails view as an example. There article is here.
Inshort, your route provider shall look like this;
public class RouteProvider : IRouteProvider
{
private const string NAMESPACES = "Nop.Plugin.Misc.Custom.Controllers";
private const string CONTROLLER = "MiscCustom";
public void RegisterRoutes(RouteCollection routes)
{
//Public Override
routes.MapGenericPathRoute("Plugin.Misc.Custom.GenericUrl",
"{generic_se_name}",
new { controller = "Common", action = "GenericUrl" },
new[] { NAMESPACES });
}
public int Priority
{
get { return Int32.Max; }
}
}
I am not sure whether i understand your question
can you try it by adding
id = UrlParameter.Optional
here any id parameter is optional it wont throw that error
Related
I am trying to do some routing in ASP.NET MVC, I wanted URLs to basically look like this for a certain area (AdminArea Section)
http://localhost/admin/folders/folder1/folder2
I want the structure above, however I am running into a few issues, which I cannot resolve.
The first issue is that sometimes the URL ends up as a querystring like
?permalink=foldername
which is not what I want.
Here is the route I have defined.
context.MapRoute
(
"folder_route",
"Administration/folders/{*permalink}",
defaults: new { controller = "folders", action = "Index",permalink = UrlParameter.Optional },
constraints: new { permalink = new FolderURLConstraint() }
);
The constraint:
public class FolderURLConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if (values[parameterName] != null)
{
var permalink = values[parameterName].ToString();
return new FolderDAL().ValidateFolderURLSlug(permalink);
}
return false;
}
}
The validation:
public bool ValidateFolderURLSlug(string folderName)
{
if (!string.IsNullOrEmpty(folderName))
{
string[] splitFolder = folderName.Split(new string[] { "/"}, StringSplitOptions.RemoveEmptyEntries);
string lastValue = splitFolder[splitFolder.Length - 1];
string[] folderNames = GetFolderBreadCrumb(lastValue.ToLower()).Select(p => p.FolderName.ToLower()).ToArray();;
splitFolder = splitFolder.Select(s => s.ToLower()).ToArray();
bool isEqual = Enumerable.SequenceEqual(splitFolder, folderNames);
return isEqual;
}
return false;
}
The index page:
#grid.GetHtml(tableStyle: "table table-bordered table-condensed table-striped",
columns:grid.Columns(
grid.Column("FolderName", "Folder Name",
format:(item) => Html.ActionLink((string)item.FolderName, "index", "folders", new {permalink=item.FolderName}, null)),
grid.Column("DateCreated", "Date Created"),
grid.Column(header:"Action", format:
#<text>
<span class="fa fa-trash"></span>
<span class="fa fa-edit"></span>
</text>)
)
)
I would like the HTML.ActionLink to generate urls like /folder1/folder2 so for example if you click on pages folder you'd get a url like /pages but when you click home folder in pages you'd get a url like /pages/home etc
I can't get that to work, also for some reason it seems to add the querystring after the first hierarchy drop.
so on the main page (/folders/) when you click a folder you'd go to
/pages/
but the folder inside pages (lets say home)
will have a link like /folders?permalink=Home
However if I type /pages/home, this still works.
Any help would be appreciated.
Hi Agent47 i whould consider using another aproach.
Normally i create a custom handler and a custom html helper to override Html.ActionLink.
RouteConfig:
routes.MapRoute(
"IUrlRouteHandler",
"{*url}",
new[] { "YourApp.Controllers" }).RouteHandler = new UrlRouteHandler();
So, you need to implement a class named UrlRouteHandler that will receive any "url" for example http://localhost/admin/folders/folder1/folder2
public sealed class UrlRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var url = requestContext.HttpContext.Request.Url.ToString();
var routeData = requestContext.RouteData.Values;
// manage your rules here!
routeData["controller"] = "YourController";
routeData["action"] = "YourAction";
return new MvcHandler(requestContext);
}
}
Use url variable above to manage your rules and don't forget to provide the controller and action names. You can look url in database, in static classes, split all segments to decide which controller and action your application will response.
I build a site in mvc3 , i want to restrict my site on firefox .
i mean to say that when anyone open my site on firefox it open correctly but when anyone opens it on chrome or IE it give an customze error . I am using c# with mvc3
You could write a global action filter which will test the User-Agent HTTP request header:
public class FireFoxOnlyAttribute : ActionFilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
var userAgent = filterContext.HttpContext.Request.Headers["User-Agent"];
if (!IsFirefox(userAgent))
{
filterContext.Result = new ViewResult
{
ViewName = "~/Views/Shared/Unauthorized.cshtml"
};
}
}
private bool IsFirefox(string userAgent)
{
// up to you to implement this method. You could use
// regular expressions or simple IndexOf method or whatever you like
throw new NotImplementedException();
}
}
and then register this filter in Global.asax:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new FireFoxOnlyAttribute());
}
You are looking for the user-agent of the user connected to your website, which may be retrieved via this call in your controller:
Request.UserAgent
Not that I agree with such a pattern, though.
This is a simple javascript function you may add to your code and perform the actions against.
function detect_browser() {
var agt=navigator.userAgent.toLowerCase();
if (agt.indexOf("firefox") != -1) return true;
else{
window.location="";//Here within quotes write the location of your error page.
}
}
On main page you may call the function on page load event. Though this practice is not recommended.
You could test the Request.UserAgent as part of a constraint on the route.
For example, you could define a route constraint routine as follows:
public class UserAgentConstraint : IRouteConstraint
{
private string requiredUserAgent;
public UserAgentConstraint(string agentParam)
{
requiredUserAgent = agentParam;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return httpContext.Request.UserAgent != null && httpContext.Request.UserAgent.Contains(requiredUserAgent);
}
}
Then add the following constraint to a route:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, //Parameter defaults
new { customConstraint = new UserAgentConstraint("Firefox") } //Constraint
);
In my MVC3 application, I have an action that routes to a custom action depending on an object selected.
public ActionResult SearchCityState(string city, string state, string searchTerm)
{
city = Server.HtmlEncode(city);
state = Server.HtmlEncode(state);
searchTerm = Server.HtmlEncode(searchTerm);
// now build the search object
...
return DoSearch(sourceRequestObject);
}
public ActionResult SearchState(string state, string searchTerm)
{
state = Server.HtmlEncode(state);
searchTerm = Server.HtmlEncode(searchTerm);
// now build the search object
...
return DoSearch(sourceRequestObject);
}
Those two methods do a bit of work in populating an object and calling the following DoSearch() method in the class and are selected based on some logic:
public ActionResult DoSearch(FeederService.SearchRequestObject sourceRequestObject)
{
...
var model = new MyAppMVC.Models.ResultsModel();
var page = model.GetData(sourceRequestObject);
return View(page);
}
Here's my model class:
public class ResultsPage
{
public DataSet dsResults { get; set; }
public Int32 actualNumberOfResults { get; set; }
public int numberOfResultsReturned { get; set; }
}
public class ResultsModel
{
...
public ResultsPage GetData(FeederService.SearchRequestObject sourceRequestObject)
{
var page = new ResultsPage();
...
page.dsResults = myWcfFeederClient.GetData(sourceRequestObject);
if (page.dsResults != null)
{
page.actualNumberOfResults = Convert.ToInt32(page.dsResults.Tables[1].Rows[0]["ActualNumberOfResults"].ToString());
page.numberOfResultsReturned = Convert.ToInt16(page.dsResults.Tables[1].Rows[0]["NumberOfResultsReturned"].ToString());
}
return page;
}
}
I have a view defined in /Results/SearchResults.cshtml that I want to route all requests to, as the output will be the same for all
The issue is that the initially selected action name is the default selected view. ie. if SearchCityState() is called, the following exception is thrown:
The view 'SearchCityState' or its
master was not found or no view engine
supports the searched locations. The
following locations were searched:
~/Views/Results/SearchCityState.aspx
~/Views/Results/SearchCityState.ascx
~/Views/Shared/SearchCityState.aspx
~/Views/Shared/SearchCityState.ascx
~/Views/Results/SearchCityState.cshtml
~/Views/Results/SearchCityState.vbhtml
~/Views/Shared/SearchCityState.cshtml
~/Views/Shared/SearchCityState.vbhtml
... and similar for SearchState(). I'm familiar with this issue, but I can't recall how to route all requests to that one view.
Thanks.
UPDATE
Here are the routes I have defined:
routes.MapRoute(name: "CityHomePage", url: "{city}-{state}", defaults: new { controller = "Home", action = "GeoHomePage" });
routes.MapRoute(name: "CityStateResults", url: "{city}-{state}/{searchTerm}", defaults: new { controller = "Results", action = "SearchCityState" });
... and off of a link defined as:
My CityState Link
I'm ending up with the following error:
The view 'SearchCityState' or its
master was not found or no view engine
supports the searched locations. The
following locations were searched:
~/Views/Results/SearchCityState.aspx
~/Views/Results/SearchCityState.ascx
~/Views/Shared/SearchCityState.aspx
~/Views/Shared/SearchCityState.ascx
~/Views/Results/SearchCityState.cshtml
~/Views/Results/SearchCityState.vbhtml
~/Views/Shared/SearchCityState.cshtml
~/Views/Shared/SearchCityState.vbhtml
Use another overload of the View() method, which takes the view name as the 1st parameter:
public ActionResult DoSearch(FeederService.SearchRequestObject sourceRequestObject)
{
...
var model = new MyAppMVC.Models.ResultsModel();
var page = model.GetData(sourceRequestObject);
return View("SearchResults", page);
}
(The MSDN article isn't helpful, but the answer doesn't feel complete)
So I have a method that accepts some JSON data and binds the data to some variables. The route only has the method name in it, nothing else.
Is it possible to have a route constraint that looks at the POST data and checks some variables to determine whether it is the correct route?
The methods:
public ActionResult GetDataV1(string userId)
{
// Do stuff one way
}
public ActionResult GetDataV2(string userId)
{
// Do stuff another way
}
The routes:
routes.MapRoute(
"API GetData V1",
"GetData",
new { controller = "API", action = "GetDataV1" },
new { version = new APIVersionRoutingConstraint("1") }
);
routes.MapRoute(
"API GetData V2",
"GetData",
new { controller = "API", action = "GetDataV2" },
new { version = new APIVersionRoutingConstraint("2") }
);
The client would post { "version":"1", "userId":"mrjohn" } to /GetData and would get response from GetDataV1. The APIVersionRoutingConstraint would make sure the right method is called depending on the version variable.
Would it be good practice to try to deserialise the request stream inside the constraint? Maybe it would be better to put the version in the URL like /GetData/1 and the other variables in the JSON body?
Rather than trying to check the version as a route constraint, why not go to one Action that then checks the version to execute the appropriate work?
I haven't tested this, but the idea is:
public ActionResult GetData(string userId, int version)
{
switch(version)
{
case 1:
return GetDataV1(userId);
case 2:
return GetDataV2(userId);
// You could always add more cases here as you get more versions,
// and still only have the one route
default:
// Probably more appropriate to return a result that contains error info,
// but you get the idea
throw new ArgumentOutOfRangeException("version");
}
}
// Made these private since they are called from the public action
private ActionResult GetDataV1(string userId)
{
// Do stuff one way
}
private ActionResult GetDataV2(string userId)
{
// Do stuff another way
}
And then you only need the one route
routes.MapRoute(
"GetData",
"GetData",
new { controller = "API", action = "GetData" },
new { version = "\d+" } // Require a numeric version
);
Made the APIVersionRoutingConstraint like this:
public class APIVersionRoutingConstraint : IRouteConstraint
{
private string _versionNumber;
public APIVersionRoutingConstraint(string versionNumber)
{
_versionNumber = versionNumber;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
string version = null;
if (httpContext.Request.ContentType.Contains("application/json"))
{
string body = new StreamReader(httpContext.Request.InputStream).ReadToEnd();
httpContext.Request.InputStream.Position = 0;
var vals = new JavaScriptSerializer().DeserializeObject(body) as Dictionary<string, object>;
if (vals.ContainsKey("version"))
version = vals["version"].ToString();
}
// Must have version to pass constraint
return !string.IsNullOrEmpty(version) && version == _versionNumber;
}
}
It's not super-efficient I guess, but it gets the job done. Only the route that matches the version in the POST body gets used.
I'm trying to read the a parameter that I've defined in a route from inside the controller.
The route:
routes.MapRoute(
"BusinessVoice", // Route name
"business/{controller}/{action}/{id}", // URL with parameters
new { controller = "Voice", action = "Index",
id = UrlParameter.Optional, locale = "business" } // Parameter defaults
);
From inside the controller I'd like to be able to read the route parameter locale, but have not idea where to look for it.
The controller:
namespace www.WebUI.Controllers
{
public class VoiceController : Controller
{
public VoiceController()
{
... want to read the locale param here
}
public ViewResult Index(string locale)
{
return View();
}
}
}
Any help is appreciated!
Dave,
This is from my basecontroller but you should be able to do exactly the same from a top level one too:
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
var locale = requestContext.RouteData.Values["locale"].ToString() ?? System.Globalization.CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
base.Initialize(requestContext);
}
good luck
jim
public VoiceController()
{
var locale = this.RouteData.Values["locale"];
}