Multiple HttpRoute in ASP.NET MVC 4 - 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.

Related

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.

problems with routes in areas

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

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]+$" }
);

MVC4 Web-API - Route mapping help required

i have a controller named
Products
and i have three actions in it
Product : takes an int returns a model(class) object
Category : takes a string returns array of model(class) object
All : no parameters return array of model(class) object
what i was trying was the following mapping
Product -> api / products / product / 1
Category -> api / product / category / sauces
All -> api / product / all
as the names of action support the URL structure so i was trying a general route which is
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
all else is working but i am getting the following error when i use this http://localhost:2271/api/products/category/2 URL
<Error>
<Message>
No HTTP resource was found that matches the request URI 'http://localhost:2271/api/products/category/2'.
</Message>
<MessageDetail>
No action was found on the controller 'Products' that matches the request.
</MessageDetail>
</Error>
on the other hand this URL api/products/category/?cat=abc & api/products/category?cat=abc is working fiine...... [cat is my receiving parameter name]
help !!!
on the other hand this URL api/products/category/?cat=abc &
api/products/category?cat=abc is working fiine
Yes that's right, it will, and that's the RPC style of doing it. If you want to maintain the RESTful way of doing it you can have the following route configuration:
config.Routes.MapHttpRoute(
name: "ProductById",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ProductByCategory",
routeTemplate: "api/{controller}/category/{cat}"
);
config.Routes.MapHttpRoute(
name: "DefaultByAction",
routeTemplate: "api/{controller}/{action}",
defaults: new { action = "Get" }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
And your controller will look something like this:
public class ProductsController : ApiController
{
// api/products/
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// api/products/5
[HttpGet]
public string Get(int id)
{
return "Product" + id;
}
// api/products/category/fruits
[HttpGet]
public string Category(string cat)
{
return "Product " + cat;
}
}
NOTE: I returned strings for simplicity's sake but I assume you will return an instance of a Product, and you can easily change the code to do so.

Web.API and FromBody

I'm a bit confused. I have a controller (derived from ApiController) that has the following method:
[ActionName("getusername")]
public string GetUserName(string name)
{
return "TestUser";
}
My routing is set up like this:
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I kept getting a 400 error when I try to hit /api/mycontroller/getusername/test with a GET in fiddler.
I found that everything worked when I added [FromBody] to the name parameter in GetUserName.
I was somehow thinking that [FromBody] was used for HttpPost, signalling that the parameter was in the body of the post, and would therefore not be needed for a GET. Looks like I was wrong though.
How does this work?
You need to either change your routing to:
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{name}",
defaults: new { name = RouteParameter.Optional }
);
or change the name of your parameter to:
[ActionName("getusername")]
public string GetUserName(string id)
{
return "TestUser";
}
Note: Additional routing parameters must match method parameter names.
You could also do the following if it is closer to what you were looking for:
// GET api/user?name=test
public string Get(string name)
{
return "TestUser";
}
This assumes you are using an ApiController named UserController and allows you to pass your name parameter as a query string. This way, you don't have to specify the ActionMethod but rather rely on the HTTP verb and matching route.