Web.API and FromBody - asp.net-mvc-4

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.

Related

CreateRef method migrated to .Net Core results in 404, how to implement Create Relationships in OData with .Net Core

I have 2 POCOs, Lessons and Traits with int PKs.
I have navigation properties set up such that I can successfully $expand like so:
http://localhost:54321/odata/Lessons?$expand=Traits
http://localhost:54321/odata/Traits?$expand=Lessons
My final hurdle in migrating project from Net 461 to .Net Core 2 is Creating Relationships.
Specifically, when I try to call the following method, with the following request, I get a 404.
[AcceptVerbs("POST", "PUT")]
public async Task<IActionResult> CreateRef(
[FromODataUri] int key, string navigationProperty, [FromBody] Uri link)
{
.... Do Work
}
Postman request:
http://localhost:54321/odata/Lessons(1)/Traits/$ref
body:
{
"#odata.id":"http://localhost:54321/OData/traits(1)"
}
The following is my Startup.Configure method.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
var builder = ConfigureOdataBuilder(app);
app.UseMvc(routeBuilder =>
{
routeBuilder.Select().Expand().Filter().OrderBy().MaxTop(null).Count();
routeBuilder.MapODataServiceRoute("ODataRoute", "odata", builder.GetEdmModel());
// Work-around for #1175
routeBuilder.EnableDependencyInjection();
routeBuilder.MapRoute(name: "default", template: "{controller=Home}/{action=Index}/{id?}"); // enable mvc controllers
});
}
private ODataConventionModelBuilder ConfigureOdataBuilder(IApplicationBuilder app)
{
var builder = new ODataConventionModelBuilder(app.ApplicationServices);
builder.EntitySet<Lessons>(nameof(Lessons));
builder.EntitySet<Traits>(nameof(Traits));
return builder;
}
Question: How do I reach this controller method?
Things I have tried,
Rename CreateRef to CreateLink and Create
Followed every link in these Git Issues, here and
here.
Read up on Attribute Routing spec.
Tried solution based off this DeleteRef in this Web Api oData v4 $ref 404 or server error
Tried explicitly defining route with [ODataRoute("Lessons({key})/{navigationProperty}")]
It was a long way, but I finally found the answer.
[ODataRoute("lessons({lessonId})/traits({traitId})/$ref")]
public IActionResult CreateRef([FromODataUri] int lessonId, [FromODataUri] int traitId)
{
//do work
}
Important: You have to call the id-params as I did. Don´t just call them Id - otherwise you´ll get a 404.
One more thing...
For those who tried the way from the microsoft docs - the Api-Names changed.. You don´t need them for this task, but if you have to convert an Uri to an OData-Path, here is an Uri-Extension doing this for you:
public static Microsoft.AspNet.OData.Routing.ODataPath CreateODataPath(this Uri uri, HttpRequest request)
{
var pathHandler = request.GetPathHandler();
var serviceRoot = request.GetUrlHelper().CreateODataLink(
request.ODataFeature().RouteName,
pathHandler,
new List<ODataPathSegment>());
return pathHandler.Parse(serviceRoot, uri.LocalPath, request.GetRequestContainer());
}
If you have an Uri like this: http://localhost:54321/OData/traits(1) you can split this into OData-segments to get e.g. the navigation: returnedPath.NavigationSource or the specified key: returnedPath.Segments.OfType<KeySegment>().FirstOrDefault().Keys.FirstOrDefault().Value

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.

Asp.net core Custom routing

I am trying to implement custom routing on an asp.net core application.
The desired result is the following:
http://Site_URL/MyController/Action/{Entity_SEO_Name}/
Entity_SEO_Name parameter will be a unique value saved into the database that it is going to help me identify the id of the entity that I am trying to display.
In order to achieve that I have implemented a custom route:
routes.MapMyCustomRoute(
name: "DoctorDetails",
template: " {controller=MyController}/{action=TestRoute}/{name?}");
public class MyTemplateRoute : TemplateRoute
{
public override async Task RouteAsync(RouteContext context)
{
//context.RouteData.Values are always empty. Here is the problem.
var seo_name = context.RouteData.Values["Entity_SEO_Name"];
int entityId = 0;
if (seo_name != null)
{
entityId = GetEntityIdFromDB(seo_name);
}
//Here i need to have the id and pass it to controller
context.RouteData.Values["id"] = entityId;
await base.RouteAsync(context);
}
}
My controller actionresult:
public ActionResult TestRoute(int id)
{
var entity = GetEntityById(id);
return Content("");
}
The problem with this approach is that the context.RouteData.Values are always empty.
Any ideas on how to move forward with this one ?
Your solution too complicated. You can have route template like
template: "{controller=Home}/{action=Index}/{seo?}"
and controller action just like
public ActionResult TestRoute(string seo)
{
var entity = GetEntityBySeo(seo);
return Content("");
}
It is enough, asp.net mvc is smart enough to bind seo variable to the parameter from url path.

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.

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.