Generating link using urlhelper in MVC 6 controller (vNext) - asp.net-mvc-routing

I am trying to learn the new MVC 6. I never used the Microsoft MVC framework before.
I am trying to enhance an simple tutorial web API by adding links to different actions of my controllers using the URL (urlhelper) of the controller (Home controller showing what the API can do).
But when I use:
this.Url.Action("Get", new {id = id});
I get a query string with the URL. I'd like a more restful-style URL.
I thought when using the attributes routing, I do not have to map a specific route like I see in old tutorials of WebApi.
Do I have to map a route ?
What do I have to do to get a more restful URL style ?

You can add a name to the route attributes in your controller, then use the extension method IUrlHelper.RouteUrl to generate the url.
For example, given the following web api controller:
[Route("api/[controller]")]
public class UsersController : Controller
{
// GET: api/users
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/users/5
[HttpGet("{id}", Name ="UsersGet")]
public string Get(int id)
{
return "value";
}
//additional methods...
}
You can generate the url api/users/123 for the specific get by id action using #Url.RouteUrl("UsersGet", new { id = 123 }).
The problem when using the Url.Action extension method is that if you have a controller like in the example above with 2 actions named "Get", this will use the route for the action without parameters and generate /api/Users?id=123. However if you comment that method, you will see that #Url.Action("Get", "Users", new { id = 123 }) also gives you the expected url.

Related

How to design multiple ways to invoke REST API

I am using ASP.NET Web API. I want to REST uri to be
GET /api/v1/documents/1234/download or
GET /api/v1/documents/1234?act=download or
GET /api/v1/documents?id=1234&act=download
Is it possible to have multiple ways to call REST API Url? Is it recommended?
I am using Attribute Routes only
[RoutePrefix("api/v1")]
public class DocumentController : ApiController
{
private readonly DomainService _domainService;
public DocumentController(DomainService domainService)
: base(domainService)
{
_domainService = domainService ?? throw new ArgumentNullException(nameof(domainService));
}
[HttpGet]
[Route("documents/{id:int}")]
public async Task<IHttpActionResult> DownloadDocument([FromUri]int id, [FromUri]string act)
{
if (string.IsNullOrEmpty(act) || act.ToUpper() != "DOWNLOAD")
{
return BadRequest("Invalid action parameter.");
}
return await service.DownloadFile(id);
}
}
with above code only GET /api/v1/documents/1234?act=download works. Is it possible to configure route in a such way that all 3 routes will invoke same action method?
You can add as many Route attributes as required to each method.
So you could do this to your method:
[Route("documents")] // matches /documents?id=123&act=download
[Route("documents/{id:int}")] // matches /documents/123?act=download
[Route("documents/{id:int}/{act}")] // matches /documents/123/download
Personally I think this is quite long-winded, and would try to stick to a single style (the last one if I could choose), but I guess it could depend on your requirements.

Web API 2.2 Content Negotiation with file extensions

I am working on a Web API and I want to use Content Negotiation with file extensions to allow browser clients to specify the content they want to receive. For instance
http://localhost:54147/data.xslx.
According to this article (http://msdn.microsoft.com/en-us/magazine/dn574797.aspx) I should be able to setup routing with something like this
//setup default routes
config.Routes.MapHttpRoute(
name: "Default",
routeTemplate: "{controller}/{id}",
defaults: new {id = RouteParameter.Optional}
);
//setup routes with extensions
config.Routes.MapHttpRoute(
name: "Url extension",
routeTemplate: "{controller}/{action}.{ext}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Here is my simple controller
public class TestController : ApiController
{
public HttpResponseMessage Get()
{
var items = new[] {"test1", "test2", "test3"};
return Request.CreateResponse(HttpStatusCode.OK, items);
}
}
using this url
http://localhost:54147/test/get.xlsx
I always get the browser default (xml in chrome, json in IE11).
or possibly
http://localhost:54147/test.xlsx
to which I get the error
No HTTP resource was found that matches the request URI 'http://localhost:54147/test.xlsx'.
I should be able to use my custom formatter. But it's not happening. Here is the constructor of my custom formatter.
public ExcelFormatter()
{
MediaTypeMappings.Add(new UriPathExtensionMapping("xlsx", ContentType.Excel));
SupportedMediaTypes.Add(new MediaTypeHeaderValue(ContentType.Excel));
}
Again according to the article this should help the API Content Negotiator use my custom formatter. I appreciate any help.
As the question is old, but is still without an answer:
Generally this links should help:
How to build media formatter
Microsofts words about content negotiation
To the code in the question:
it seems you need to extend from BufferedMediaTypeFormatter(sync) or MediaTypeFormatter`(async)
you need to make your formatter known to HttpConfiguration.Formatters (link)
You probably want to do this in an config for the complete application.
For testing you could add in to a single ApiController like following.
untested example
public class TestController : ApiController
{
TestController() {
Configuration.Formatters.Add(new ExcelFormatter());
}
public HttpResponseMessage Get()
{
var items = new[] {"test1", "test2", "test3"};
return Request.CreateResponse(HttpStatusCode.OK, items);
}
}
```

RESTful Web API with Associations. Is it possible?

I have written a REST service using Web API and after reading sections of this Web API Design from Brian Mulloy, was trying to figure out how I could implement associations with Web API.
Web API Design Extract:
Associations
Resources almost always have relationships to other
resources. What's a simple way to express these relationships in
aWebAPI?
Let's look again at the API we modeled in nouns are good,
verbs are bad -theAPI that interacts with our dogs resource.
Remember, we had two base URLs: /dogs and dogs/1234.
We're using HTTP
verbs to operate on the resources and collections. Our dogs belong to
owners. To get all the dogs belonging to a specific owner, or to
create a new dog for that owner, do a GET or a POST:
GET /owners/5678/dogs
POST /owners/5678/dogs
Now, the relationships can be
complex. Owners have relationships with veterinarians, who have
relationships with dogs, who have relationships with food, and so on.
It's not uncommon to see people string these together making a URL 5
or 6 levels deep. Remember that once you have the primary key for one
level, you usually don't need to include the levels above because
you've already got your specific object. In other words, you shouldn't
need too many cases where a URL is deeper than what we have above
/resource/identifier/resource.
So I tried to add a controller method for the association like follows:
public class EventsController : ApiController
{
// GET api/events
public IEnumerable<Event> Get()
{
// get list code
}
// GET api/events/5
public Event Get(int id)
{
// get code
}
// POST api/events
public void Post([FromBody]Event evnt)
{
// add code
}
// POST api/events/5
public void Post(int id, [FromBody]Event evnt)
{
// update code
}
// DELETE api/events/5
public void Delete(int id)
{
// delete code
}
// GET api/events/5/guests
public IEnumerable<Guest> Guests(int id)
{
// association code
}
}
I also modified my route templates to the following:
config.Routes.MapHttpRoute("ApiWithAssociations",
"api/{controller}/{id}/{action}");
config.Routes.MapHttpRoute("DefaultApi",
"api/{controller}/{id}",
new { id = RouteParameter.Optional });
Unfortunately, when I do an update/post of the event resource I now get a HTTP 500 Internal Server Error with a response body stating
Multiple actions were found that match the request
I've tried modifying the route templates in conjunction with adding System.Web.Http.HttpPostAttribute (and other HTTP verbs) as well but to no avail.
Has anyone tried this and got it working? Any help would be appreciated. If it is absolutely not possible to have multiples for an http verb then I guess I'll have to abandon associations with my REST service.
EDIT: SOLUTION
Using Radim Köhler's answer, I was able to get this working. Add the HttpGetAttribute to the Guests method like so:
// GET api/event/5/guests
[HttpGet]
public IEnumerable<Guest> Guests(int id)
{
// association code
}
And added an addition route to cater for the default GET action like follows:
config.Routes.MapHttpRoute("DefaultGet",
"api/{controller}/{id}",
new {action = "Get"},
new {httpMethod = new HttpMethodConstraint(HttpMethod.Get)});
config.Routes.MapHttpRoute("ApiWithAssociations",
"api/{controller}/{id}/{action}");
config.Routes.MapHttpRoute("DefaultApi",
"api/{controller}/{id}",
new {id = RouteParameter.Optional});
The solution, could be in an explicit POST mapping
Just add new definition, which will be used for events/5 POST
// explicit Post() mapping
config.Routes.MapHttpRoute(
name: "DefaultPost",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "Post" }
, constraints: new { httpMethod = new HttpMethodConstraint(HttpMethod.Post) }
);
// existing
config.Routes.MapHttpRoute("ApiWithAssociations",
"api/{controller}/{id}/{action}");
config.Routes.MapHttpRoute("DefaultApi",
"api/{controller}/{id}",
new { id = RouteParameter.Optional });

Dependency Injection & Model Binding (ASP MVC, Autofac), When to use what?

This is more like a conceptual question. When to use Model Binding (in ASP.NET MVC Framework) and when to inject objects using IoC (lets say Autofac here) ?
One specific scenario is like lets say, I have the following action method
public ActionResult EditProfile(string UserId)
{
// get user object from repository using the the UserId
// edit profile
// save changes
// return feedback
}
In the above scenario, is it possible to inject a user object to action method such that it automatically gets the user object using the UserId ? The resulting signature being:
public ActionResult EditProfile(UserProfile userObj) //userObj injected *somehow* to automatically retreive the object from repo using UserId ?
Sorry if it all doesn't makes sense. It`s my first time using IoC.
EDIT:
This is the way to do it > http://buildstarted.com/2010/09/12/custom-model-binders-in-mvc-3-with-imodelbinder/
You can do what you need using a custom action filter. By overriding OnActionExecuting, we have access to the route data, and the action parameters of the action that will be executed. Given:
public class BindUserProfileAttribute : ActionFilterAttribute
{
public override OnActionExecuting(FilterContext filterContext)
{
string id = (string)filterContext.RouteData.Values["UserId"];
var model = new UserProfile { Id = id };
filtextContext.ActionParameters["userObj"] = model;
}
}
This attribute allows us to create the parameters that will be passed into the action, so we can load the user object at this point.
[BindUserProfile]
public ActionResult EditProfile(UserProfile userObj)
{
}
You'll probably need to get specific with your routes:
routes.MapRoute(
"EditProfile",
"Account/EditProfile/{UserId}",
new { controller = "Account", action = "EditProfile" });
In MVC3 we get access to the new IDepedencyResolver interface, which allows us to perform IoC/SL using whatever IoC container or service locator we want, so we can push a service like a IUserProfileFactory into your filter, to then be able to create your UserProfile instance.
Hope that helps?
Model binding is used for your data. Dependency injection is used for your business logic.

ASP.NET MVC 2.0: How to read querystring value

I am trying to build a small ASP.NET MVC 2 application.I have a controller class with the below method in it
public ActionResult Index()
{
TestMvc.Models.PersonalInformation objPerson = new TestMvc.Models.PersonalInformation();
objPerson.FirstName = "Shyju";
objPerson.LastName = "K";
objPerson.EmailId="shyju#company.com";
return View(objPerson);
}
And when the page (View) being called, i can see this data there as my view has these data's displaying. Now i want to know how can i pass a query string in the url and use that id to build the PersonalInformation object.Hoe can i read the querystring value ? Where to read ?
I want the quesrtstring to be like
http://www.sitename/user/4234 where 4234 is the user id
http://www.sitename/user/4234 is not a querystring. The querystring is the part of the URL that comes after the ?, as in http://www.sitename/user?userId=42
However, the default routes that come with the MVC project template should allow you to simply change the signature of your action method to
public ActionResult Index(int id)
and you should get the desired result. You should look into how routing works in MVC if you want full control of your URLs.
Also, note that the index action is usually used for showing a list of all objects, so you probably want the Details action for showing 1 user object.
What you want is to modify your action to accept an id like so:
public ActionResult Index(string id)
{
TestMvc.Models.PersonalInformation objPerson = new TestMvc.Models.PersonalInformation();
if (!string.isNullOrEmpty(id))
{
objPerson = getPerson(id);
}
return View(objPerson);
}
Then add a route to your global.asax:
routes.MapRoute(
"MyRoute", // Route name
"user/{id}", // URL with parameters
new { controller = "mycontroller", action = "index", id = ""} // Parameter defaults
);