problems with routes in areas - asp.net-mvc-4

I have an area called Advert. This area as two route rules. But unfortunately only one of the route rules works at a time. Usually the first rule the other below would not work. but if I interswitch rules the one that comes first would work.
I would paste the rule and html links that call the rules.
Code snippet from AdvertAreaRegistration.cs
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Advert_second",
"Advert/{controller}/{action}",
new { controller = "AdvertAdmin", action = "Index" },
namespaces: new string[] { "LiveChatPrototype.Mvc.Areas.Advert.Controllers" }
);
context.MapRoute(
name: "Advert_default",
url: "Advert/{id}/{advertid}",
defaults: new { controller = "Advertisement", action = "Index", id =UrlParameter.Optional, advertid = UrlParameter.Optional },
namespaces: new string[] { "LiveChatPrototype.Mvc.Areas.Advert.Controllers" }
);
}
The html links which I use to call my rules
This is for the first rule.
This is for the second rule.
Either of the links would work if it rule comes first.
Please how can I make both rules work at the same time.

Currently the two route segments are identical
"Advert/{controller}/{action}"
"Advert/{id}/{advertid}"
Both have two dynamic segments, so asp.net mvc cannot distinguish between the two and matches the first one.
But it seems like the segments in "Advert/{id}/{advertid}" id and advertid are intended to be integers. Then you can add regex constraint in the route to distinguish between them.
like
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
name: "Advert_default",
url: "Advert/{id}/{advertid}",
defaults: new { controller = "Advertisement", action = "Index" },
constraints: new { id = #"\d+", advertid = #"\d+" }
namespaces: new string[] { "LiveChatPrototype.Mvc.Areas.Advert.Controllers" }
);
context.MapRoute(
"Advert_second",
"Advert/{controller}/{action}",
new { controller = "AdvertAdmin", action = "Index" },
namespaces: new string[] { "LiveChatPrototype.Mvc.Areas.Advert.Controllers" }
);
}
Hope this helps

Related

Remove route from RouteCollection in Asp.Net Core and add new with same route name (nopCommerce-4.00)

I want to remove existing route from RouteCollection and want to add new route with same route name in nopCommerce 4.00 via plugin
Existing route name:
//home page
routeBuilder.MapLocalizedRoute("HomePage", "",
new { controller = "Home", action = "Index" });
I Want to replace it with
routeBuilder.MapLocalizedRoute("HomePage", "",
new { controller = "CustomPage", action = "Homepage" });
I tried several ways but not get any luck.
In my case, I have to replace the robots.txt generation.
I created a new public controller in my plugin, and I copy the original action here:
public class MiscCommonController : BasePublicController
{
#region Fields
private readonly ICommonModelFactory _commonModelFactory;
#endregion Fields
#region Ctor
public MiscCommonController(
ICommonModelFactory commonModelFactory
)
{
this._commonModelFactory = commonModelFactory;
}
#endregion Ctor
#region Methods
//robots.txt file
//available even when a store is closed
[CheckAccessClosedStore(true)]
//available even when navigation is not allowed
[CheckAccessPublicStore(true)]
public virtual IActionResult RobotsTextFile()
{
var robotsFileContent = _commonModelFactory.PrepareRobotsTextFile();
return Content(robotsFileContent, MimeTypes.TextPlain);
}
#endregion Methods
}
After this I create a RouteProvider for my plugin, and I replaced the original route to my own one.
public partial class RouteProvider : IRouteProvider
{
/// <summary>
/// Gets a priority of route provider
/// </summary>
public int Priority => -1;
/// <summary>
/// Register routes
/// </summary>
/// <param name="routeBuilder">Route builder</param>
public void RegisterRoutes(IRouteBuilder routeBuilder)
{
Route route = null;
foreach (Route item in routeBuilder.Routes)
{
if (item.Name == "robots.txt")
{
route = item;
break;
}
}
if (route != null) routeBuilder.Routes.Remove(route);
routeBuilder.MapRoute(
"robots.txt",
"robots.txt",
new { controller = "MiscCommon", action = "RobotsTextFile" }
);
}
}
That's all.
After this implementation, the routing works fine, and the get request landed in my own controller, which is act like the original.
Now, I can replace the generation logic with my own.
I hope it helps.
in the RouteProvider.cs of your plugin write these codes (based on your names):
var lastExistingRoute= routeBuilder.Routes.FirstOrDefault(x => ((Route)x).Name == "HomePage");
routeBuilder.Routes.Remove(lastExistingRoute);
routeBuilder.MapRoute("HomePage", "",
new { controller = "CustomPage", action = "Homepage", });
and the below codes worked for myself version 4.20:
var lastDownloadRoute=routeBuilder.Routes.FirstOrDefault(x => ((Route)x).Name == "GetDownload");
routeBuilder.Routes.Remove(lastDownloadRoute);
routeBuilder.MapRoute("GetDownload", "download/getdownload/{guid}/{agree?}",
new { controller = "AzTechProduct", action = "GetPayed", });
There are two potential ways to deal with this in nopCommerce 4.3 that I see with a quick examination of the code.
First, you could create an IRouteProvider, add your route that has the same signature as the one you wish to 'delete' and make sure the Priority on the provider is greater than 1.
Doing this will basically override the default route built into Nop. This is my preferred method.
public partial class RouteProvider: IRouteProvider
{
public void RegisterRoutes(IEndpointRouteBuilder endpointRouteBuilder)
{
var pattern = string.Empty;
if (DataSettingsManager.DatabaseIsInstalled)
{
var localizationSettings = endpointRouteBuilder.ServiceProvider.GetRequiredService<LocalizationSettings>();
if (localizationSettings.SeoFriendlyUrlsForLanguagesEnabled)
{
var langservice = endpointRouteBuilder.ServiceProvider.GetRequiredService<ILanguageService>();
var languages = langservice.GetAllLanguages().ToList();
pattern = "{language:lang=" + languages.FirstOrDefault().UniqueSeoCode + "}/";
}
}
// Handle the standard request
endpointRouteBuilder.MapControllerRoute("Wishlist", pattern + "wishlist/{customerGuid?}",
new { controller = "MyShoppingCart", action = "Wishlist" });
return;
}
public int Priority => 100;
}
The key to the code above is the Priority value. This route will get added to the list first and will therefore take precedence over the default route. Using this technique eliminates the need to delete the default route.
The second possible method turns out to not work because the endpointRouteBuilder.DataSources[n].Endpoints collection is read only. So, as far as I know, you can't remove mappings from that list after they have been added.

Using CacheCow to Cache Based On Parameter

I have a webapi endpoint that looks like the following in my Controller:
[HttpGet]
public IHttpActionResult GetPerson(
string term = null,
string workspace = null)
{
try
{
logger.Info("AvPerson start: " + DateTime.Now);
if (term == null)
{
return BadRequest();
}
ICoreAVData api = MvcApplication.Container.Resolve<ICoreAVData>();
List<Person> persons = new List<Person>();
persons.AddRange(api.GetAllPersonsForTerm(term, workspace));
if (persons == null)
{
return NotFound();
}
return Ok(persons);
}
catch (Exception)
{
return InternalServerError();
}
}
The term parameter can vary constantly but the workspace parameter displays what is relevant to the user. The user will not leave his own workspace, so that parameter will be constant from a user perspective.
I wonder if it is possible to have CacheCow cache based on the workspace parameter. ie. If workpace1 then cache it, if workspace2 then cache that separately.
I recognize that I will have to have add some kind of logic to invalidate that workspace specific cache. I'm not asking about that, because I believe I know how I might do that. I want to know if I can have a separate cache entry per workspace parameter.
Here is my routing setup for this controller:
config.Routes.MapHttpRoute(
name: "avperson",
routeTemplate: "api/v1/avperson/{action}/{id}",
defaults: new { controller = "avperson", id = RouteParameter.Optional }
);
Any ideas?
So the solution is to change the routing.
config.Routes.MapHttpRoute(
name: "avperson",
routeTemplate: "api/v1/{workspace}/avperson/{action}/{id}",
defaults: new { controller = "avperson", workspace = "all", id = RouteParameter.Optional }
);
Doing this will create a separate cached entry for each workspace which can then be validated or invalidated according to need.

custom method with same signature web api mvc4

I am using Web Api with ASP.NET MVC4. For Custom Get methods I am having problem, which I am explaining below.
my WebApiConfig.cs file is
public static void Register(HttpConfiguration config)
{
//1
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//2
config.Routes.MapHttpRoute(
name: "FindDetailsByAge",
routeTemplate: "api/{controller}/{action}/{age}",
defaults: new { age = RouteParameter.Optional },
constraints: new { age = #"^[0-9]+$" }
);
//3
config.Routes.MapHttpRoute(
name: "FindDetailsByName",
routeTemplate: "api/{controller}/{action}/{name}",
defaults: new { name = RouteParameter.Optional },
constraints: new { name = #"^[a-z]+$" }
);
//4
config.Routes.MapHttpRoute(
name: "FindDetailsByCountry",
routeTemplate: "api/{controller}/{action}/{country}",
defaults: new { country = RouteParameter.Optional },
constraints: new { country = #"^[a-z]+$" }
);
// for json
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
}
Here is my controller file
public class ContactController : ApiController
{
private ContactRepository contactRepository;
public ContactController()
{
this.contactRepository = new ContactRepository();
}
public IEnumerable<Contact> GetAllContact()
{
return contactRepository.GetAll();
}
public Contact GetNameByAge(int id)
{
Contact contact = contactRepository.GetNameByAge(id);
if (contact == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return contact;
}
[HttpGet]
public Contact FindDetailsByAge(int age)
{
Contact contact = contactRepository.FindDetailsByAge(age);
if (contact == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return contact;
}
[HttpGet]
public IEnumerable<Contact> FindDetailsByName(string name)
{
IEnumerable<Contact> lstContactName = contactRepository.FindDetailsByName(name);
if (lstContactName == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return lstContactName;
}
[HttpGet]
public IEnumerable<Contact> FindDetailsByCountry(string country)
{
IEnumerable<Contact> lstContactCountry = contactRepository.FindDetailsByCountry(country);
if (lstContactCountry == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return lstContactCountry;
}
}
When I am executing these methods my results are.
( I am entering the url in the firefox browser)
case - 1
input :
../api/contact/
output :
[{"Name":"Ashok","Age":60,"Country":"India"},{"Name":"Nargis","Age":30,"Country":"India"},{"Name":"Nargis","Age":35,"Country":"Iran"},{"Name":"Steve","Age":50,"Country":"South
Africa"}]
case - 2
input :
../api/contact/FindDetailsByAge/50
output :
{"Name":"Steve","Age":50,"Country":"South Africa"}
case - 3
input :
../api/contact/FindDetailsByName/nargis
output :
[{"Name":"Nargis","Age":30,"Country":"India"},{"Name":"Nargis","Age":35,"Country":"Iran"}]
case -4
input : ../api/contact/FindDetailsByCountry/india
output :
{"Message":"No HTTP resource was found that matches the request URI
'../api/contact/FindDetailsByCountry/india'.","MessageDetail":"No
action was found on the controller 'Contact' that matches the
request."}
From the above outputs you see that for case-4, it gives error.
FindDetailsByName is being executed
FindDetailsByCountry is not being executed
Now if I place the MapHttpRoute (4th case) before MapHttpRoute(3rd case) i.e. interchange the 3rd and 4th position, WebApiConfig.cs look like
...
...
//4
config.Routes.MapHttpRoute(
name: "FindDetailsByCountry",
routeTemplate: "api/{controller}/{action}/{country}",
defaults: new { country = RouteParameter.Optional },
constraints: new { country = #"^[a-z]+$" }
);
//3
config.Routes.MapHttpRoute(
name: "FindDetailsByName",
routeTemplate: "api/{controller}/{action}/{name}",
defaults: new { name = RouteParameter.Optional },
constraints: new { name = #"^[a-z]+$" }
);
then output would be
input :
../api/contact/FindDetailsByCountry/india
output :
[{"Name":"Ashok","Age":60,"Country":"India"},{"Name":"Nargis","Age":30,"Country":"India"}]
input :
../api/contact/FindDetailsByName/nargis
output :
{"Message":"No HTTP resource was found that matches the request URI
'../api/contact/FindDetailsByName/nargis'.","MessageDetail":"No action
was found on the controller 'Contact' that matches the request."}
Now
FindDetailsByName is not being executed
FindDetailsByCountry is being executed
From the above code it is clear that in the config file for routing between 3 and 4, which one comes first is being executed.
I have
method -1
public Contact FindDetailsByName(string name)
method -2
public Contact FindDetailsByCountry(string country)
You see both the method FindDetailsByName and FindDetailsByCountry is taking only string parameter and returning Contact object.
Now my question is - How will I execute both the method ? What will be MapHttpRoute ? What will be MapHttpRoute's order ?
I have been searching the solution for last two days, but did not get any. I know I have given long description of the code, please read it patiently.
Thanks.
You should just need to have a single mapping that covers both. The api/{controller}/{action}/ covers the "api/contact/FindDetailsByName/" and "api/contact/FindDetailsByCountry".
With your current setup, the country one would take effect first, and the name one would be skipped because they have the same signature (controller/action/parameter).
Change your methods to accept a generic variable name, such as public IEnumerable<Contact> FindDetailsByName(string searchValue) and use a single route for both:
config.Routes.MapHttpRoute(
name: "FindDetailsBySearchValue",
routeTemplate: "api/{controller}/{action}/{searchValue}",
defaults: new { searchValue = RouteParameter.Optional },
constraints: new { searchValue = #"^[a-z]+$" }
);
OR don't use templates on the action, as in this answer: Web.API MapHttpRoute parameters
config.Routes.MapHttpRoute(
name: "FindDetailsByCountry",
routeTemplate: "api/{controller}/FindDetailsByCountry/{country}",
defaults: new { country = RouteParameter.Optional },
constraints: new { country = #"^[a-z]+$" }
);
//3
config.Routes.MapHttpRoute(
name: "FindDetailsByName",
routeTemplate: "api/{controller}/FindDetailsByName/{name}",
defaults: new { name = RouteParameter.Optional },
constraints: new { name = #"^[a-z]+$" }
);

URL does not route to controller

I have created an ImageController to serve images from a location in my project. Now I'm trying to get it to route from "/Image/file.png" to my Controller, but I can't seem to get it right. The controller is never been called (I set a breakpoint in the first line of the action and it never breaks).
My route:
routes.MapRoute(
"Image",
"Image/{file}",
new { controller = "Image", action = "Render", file = "" }
);
My ImageController:
public class ImageController : Controller
{
//
// GET: /Image/
public ActionResult Render(string file)
{
var path = this.getPath(file);
System.Diagnostics.Debug.WriteLine(path);
if (!System.IO.File.Exists(path))
{
return new HttpNotFoundResult(string.Format("File {0} not found.", path));
}
return new ImageResult(path);
}
private string getPath(string file)
{
return string.Format("{0}/{1}", Server.MapPath("~/Content/Images"), file);
}
}
Why isn't my project routing from "Images/{file}" to my controller?
It's probably because the request is being routed to the static file handler due to the .png extension in the url. You could try passing the filename and suffix as separate parameters, something like this:
routes.MapRoute(
"Image",
"Image/{filename}/{suffix}",
new { controller = "Image", action = "Render", filename = "", suffix = "" }
);
Your controller action then becomes:
public ActionResult Render(string filename, string suffix)
And it should match urls like this:
/Image/file/png

Multiple HttpRoute in ASP.NET MVC 4

I'm trying to make a RouteConfig in Web API, that allows following patterns:
Patterns:
/api/{controller}
/api/{controller}/{id} (int, optional)
/api/{controller}/{action}
/api/{controller}/{action}/{id} (int, optional)
Use cases:
/api/profile/ (get all profiles)
/api/profile/13 (get profile number 13)
/api/profile/sendemail/ (send email to all profiles)
/api/profile/sendmail/13 (send email to profile number 13)
What I'm trying is the following:
routes.MapHttpRoute(
name: "ControllerAndID",
routeTemplate: "api/{controller}/{id}",
defaults: null,
constraints: new { id = #"^\d+$" } // Dekkar heiltölur eingöngu í id parameter
);
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "Get", id = RouteParameter.Optional }
);
The error I'm getting is:
Multiple actions were found that match the request:
\r\nMinarSidur.Models.DataTransfer.UserProfileDTO sendmail(System.String)
on type
MinarSidur.Controllers.ProfileController\r\nMinarSidur.Models.DataTransfer.UserProfileDTO
sendpaycheck(System.String) on type MinarSidur.Controllers.ProfileController
Can you help me accomplishing this?
Your exception was actually complaining about their being a conflict between these two methods on the Profile controller:
sendpaycheck(string)
sendmail(string)
Not the Get and Get(?); although this would also be an issue.
Really, when carrying out RPC actions that make changes or trigger actions you should use the POST verb. By doing this your routing issues mentioned above should be resolved.
Updated
Have you considered a more resource centric approach to your problem? In all cases here the resource is "Profile" and it appears to have a unique id of x. It appears to also have two other possible unique id's email and ssn?
If these were acceptable URL's to you
http://localhost/api/profile
http://localhost/api/profile/x
http://localhost/api/profile/?email=myemail#x.com
http://localhost/api/profile/?ssn=x
you could use:
public class ProfileController : ApiController
{
public string Get(int id)
{
return string.Format("http://localhost/api/profile/{0}", id);
}
public string Get([FromUri] string email = null, [FromUri] int? ssn = null)
{
if (!string.IsNullOrEmpty(email))
{
return string.Format("http://localhost/api/profile/?email={0}", email);
}
if (ssn.HasValue)
{
return string.Format("http://localhost/api/profile/?ssn={0}", ssn.Value);
}
return "http://localhost/api/profile";
}
}
With just the standard webapi routing:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
But
If you did want to carry on with /email and /ssn you may have issues with the email... specifically with the "." in the email address and this can confuse the routing engine... for this to work you must put a trailing slash i.e. http://localhost/api/profile/email/me#me.com/ I think you will find http://localhost/api/profile/email/me#me.com wont work.
This supports:
http://localhost/api/profile
http://localhost/api/profile/x
http://localhost/api/profile/email/myemail#x.com/
http://localhost/api/profile/ssn/x
I would try this and use (NB. the use of the name rpcId to differentiate the routes):
public class ProfileController : ApiController
{
public string Get(int id)
{
return string.Format("http://localhost/api/profile/{0}", id);
}
public string Get()
{
return "http://localhost/api/profile";
}
[HttpGet]
public string Ssn(int rpcId)
{
return string.Format("http://localhost/api/profile/ssn/{0}", rpcId);
}
[HttpGet]
public string Email(string rpcId)
{
return string.Format("http://localhost/api/profile/email/{0}", rpcId);
}
}
My routing would then be:
config.Routes.MapHttpRoute(
name: "ProfileRestApi",
routeTemplate: "api/profile/{id}",
defaults: new { id = RouteParameter.Optional, Controller = "Profile" }
);
config.Routes.MapHttpRoute(
name: "PrfileRpcApi",
routeTemplate: "api/profile/{action}/{rpcId}",
defaults: new { Controller = "Profile" }
);
The rules overlap. Would the validation of the Id in routing be a great loss? You could still have the Id parameter as an int within your Get actions. If that's the case I think you can remove ControllerAndID entirely and just use the second which will match all your use cases.