Why does [FromBody] ignore model binders? - rest

I'm working on an REST API in ASP.NET. One of the first things I made was a Model Binder for the model, binded with the model by a code like this:
[ModelBinder(typeof(MyModelModelBinder))]
public class MyModel { ... }
And used it in an action
public IHttpActionResult Post(MyModel model) { ... }
I realised, quite suddenly, that after I added the [FromBody] before the parameter type...
public IHttpActionResult Post([FromBody]MyModel model) { ... }
...the Model Binder gets ignored. I tried to debug it by puting a breakpoint on the beginning of the BindModel method and at the beginning of the action and realised that when I call the action I get straight to the action. When I delete the FromBody Attribute the breakpoint in the ModelBinder becomes active again.
Why can't I use both? Why is the FromBody attribute ignoring the ModelBinder?

Related

GET and PUT methods for same route don't work if in separate classes

We're using specific route attributes for all our web api routes.
We want to split up a controller class, so we're moving some update functions out to a separate controller class.
We've subsequently got an odd situation with routing.
The original controller class looked like this:
[RoutePrefix("activities")]
public class ActivitiesController : ApiController
{
...
[Route("{activityId:int:min(1)}"), HttpPut]
public void Put(int activityId, [FromBody] NewActivity value) {}
[Route("{activityId:int:min(1)}"), HttpGet]
public dynamic Get(int activityId) {}
...
}
And we then split out the updates, so we end up with this:
[RoutePrefix("activities")]
public class ActivitiesUpdateController : ApiController
{
...
[Route("{activityId:int:min(1)}"), HttpPut]
public void Put(int activityId, [FromBody] NewActivity value) {}
...
}
[RoutePrefix("activities")]
public class ActivitiesController : ApiController
{
...
[Route("{activityId:int:min(1)}"), HttpGet]
public dynamic Get(int activityId) {}
...
}
If we then try to call either endpoint:
- PUT activities/42
- GET activities/42
We get the error:
Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.
The request has found the following matching controller types:
API.Controllers.ActivitiesUpdateController
API.Controllers.ActivitiesController
If both endpoints are in the same class (either the new one or the old), everything works correctly.
Different classes raises the error.
If I dump out the endpoints using ApiExplorer, both end points are where they should be and are not repeated in the other class, so there's no obvious duplication.
I've tried renaming functions, explicit routes (i.e. ~/activities/:id), adding HttpGet & HttpPut doesn't seem to make a difference.
Is this some weird "feature" where the same endpoint with different methods have to be in the same class?
It makes no sense.
Not to worry, I came across this after I posted - don't know how I missed it before:
Multiple controllers with same URL routes but different HTTP methods

Web Api 2 Inheritance No route providing a controller name was found to match request URI

Basically I cannot get my Web Api 2 application to work.
First of all here are my requirements.
In my application I am creating a dozen of controllers ( ProductController, ItemController, SalesController...etc). There are 2 actions which are absolutely common in all my controllers:
FetchData, PostData
(Each controller then may implement a number of other methods which are sepcific to its business domain )
Instead of repeating these actions in every controllers like:
public class ProductController:ApiController{
[HttpPost]
public MyReturnJson FetchData( MyJsonInput Input){
....
return myJsonResult;
}
}
public class SalesController:ApiController{
[HttpPost]
public MyReturnJson FetchData( MyJsonInput Input){
....
return myJsonResult;
}
}
I decided to create a base controller MyBaseController:
public class MyBaseController : ApiController{
[HttpPost]
public MyReturnJson FetchData( MyJsonInput Input){
....
return myJsonResult;
}
}
with the 2 methods so every other controller would inherit them (It saves me from repeating them in every controller). The common base class has been defined and implemented in a separate assembly which is then referenced in my web project.
Then in my javascript client (using breeze) I call a specific controller like
breeze.EntityQuery.from('FetchData')
where my serviceName is 'my_api/product/'
(in the WebApiConfig, the routing table has been defined like:
config.Routes.MapHttpRoute(
name: "my_api",
routeTemplate: "my_api/{controller}/{action}"
);
But when the javascript code is executed I get the error message:
No route providing a controller name was found to match request URI
http://localhost:xxxxx/my_api/product/FetchData
If I don't use a common base class but instead repeat this method (FetchData) in every class (basically ProductController inherits directly from ApiController and not from MyBaseController) every thing works fine and my method is hit. I thing there is a problem with the inheritance scheme. Maybe there is something I don't get (first time using Web Api 2) or some constraints (routing, configuration...) I do not respect. Right now I am stuck and I would appreciate any suggestion which might point me to the right direction. Is inheritance allowed in Web Api 2?
I am not sure why your code is not working. But in the next link (http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-22#ARI) you can see an example of inheritance using attribute routing.
This is the code example:
public class BaseController : ApiController
{
[Route("{id:int}")]
public string Get(int id)
{
return "Success:" + id;
}
}
[RoutePrefix("api/values")]
public class ValuesController : BaseController
{
}
config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
(inherit: true);
}
}
I hope that it helps.

MVC Model Binding with Attribute Routing

Converting an MVC 3 app to MVC 5 and relying exclusively on Attribute Routing. All is good but I am unable to define a route for a controller method that accepts a (not so) complex object as a parameter.
If I omit the parameter in the route, my controller/method is never invoked and I receive a 404 (as I would expect). When I include the viewModel parameter as part of the route, my method is invoked but the parameter is always null.
This code worked fined before with "classic" routing and no specific route was needed to model bind my viewModel parameter. I never defined it as part of any route other than including the controller and method names. The view is unchanged.
[RoutePrefix("Text")]
[Route("{action}")]
public class TextController : Controller
{
[HttpGet]
[Route("{jobTextID}")]
public ViewResult Edit(Guid jobTextID)
{
// this (get) works fine
}
[HttpPost]
[Route("{viewModel}"]
public RedirectToRouteResult Edit(TextEditViewModel viewModel)
{
// viewModel is always null
}
}
I think you should add the id to the POST action:
[HttpPost]
[Route("{jobTextID}")]
public RedirectToRouteResult Edit(Guid jobTextID, TextEditViewModel viewModel) { }
The resource is identified by the URL, not by a parameter in the body of the request. Use jobTextID to retrieve the entity from the database and ignore the id from the viewModel.

GWT Request Factory and Editor Framework Exception

When attempting to edit a new (proxy) entity using RequestFactoryEditorDriver.edit() I am getting the following error: "Exception caught: Attempting to edit an EntityProxy previously edited by another RequestContext". I am fairly sure that this is a result of my misunderstanding of the request factory/editor framework architecture. Here is the editor code that I think pertains to this problem:
public class OrgMaintenanceWidget extends Composite implements Editor<IOrgProxy> {
... other fields ...
private IOrgEditorDriver _orgEditorDriver;
interface IOrgEditorDriver extends RequestFactoryEditorDriver<IOrgProxy, OrgMaintenanceWidget> {}
public OrgMaintenanceWidget(final IClientFactory clientFactory) {
... widget initialization ...
_orgEditorDriver = GWT.create(IOrgEditorDriver.class);
_orgEditorDriver.initialize(_clientFactory.getRequestFactory().getEventBus(),
_clientFactory.getRequestFactory(), this);
}
#UiHandler("newButton")
public void onNewButtonClick(final ClickEvent clickEvent) {
_org = _clientFactory.getCache().getOrgCache().newOrg();
_orgEditorDriver.edit(_org, _clientFactory.getRequestFactory().orgRequestContext());
}
...
}
It's the "_orgEditorDriver.edit()" line that causes the exception. The "newOrg()" method is:
public IOrgProxy newOrg() {
return _clientFactory.getRequestFactory().orgRequestContext().create(IOrgProxy.class);
}
The RequestFactory is simply:
public interface IRequestFactory extends RequestFactory {
IOrgRequestContext orgRequestContext();
}
I am sure that I'm missing something fundamental about editing a new entity. When I edit an existing entity everything is fine ... the UI components are populated automatically, and flushing the editor back to the entity works very nicely. Here's the code that initiates editing for an existing entity:
#UiHandler("newButton")
public void onNewButtonClick(final ClickEvent clickEvent) {
_org = _clientFactory.getCache().getOrgCache().newOrg();
_orgEditorDriver.edit(_org, _clientFactory.getRequestFactory().orgRequestContext());
}
Any help would be greatly appreciated, and I'll try to publish any lessons learned.
This code:
_clientFactory.getRequestFactory().orgRequestContext().create(IOrgProxy.class);
Means:
Create new orgRequestContext()
Create new IOrgProxy using this context
Edit new IOrgProxy using this context, because as docs say: "Returns a new mutable proxy that this request can carry to the server, perhaps to be persisted.", it means that the proxy is edited by this request.
This code:
_orgEditorDriver.edit(_org, _clientFactory.getRequestFactory().orgRequestContext());
Means:
Again, create new orgRequestContext() (because each invocation of getRequestFactory().orgRequestContext() provides new instance of orgRequestContext()
"Start driving the Editor and its sub-editors with data." as docs say. But as a part of it, use passed orgRequestContext() to edit passed IOrgProxy instance, so that the proxy is editable.
Because the proxy was already edited while created by other RequestContext, you get the exception, because there is fundamental rule in RequestFactory, that proxy can be edited only by one RequestContext.
See also this thread.
I think you can't create an object with one RequestContext and then edit it with another one.
So you can solve this in two ways:
Persist the created object with the RequestContext you used when you created the object. The save method should return the persisted object and this persisted object can be passed to the editor with a fresh new RequestContext
Somewhere save the RequestContext you used for creating the object and pass it to the edit function of your Driver
Solution two could look something like this:
#UiHandler("newButton")
public void onNewButtonClick(final ClickEvent clickEvent) {
IOrgRequestContext ctx = _clientFactory.getRequestFactory().orgRequestContext();
_org = ctx.create(IOrgProxy.class);
_orgEditorDriver.edit(_org,ctx );
}

Does renderaction calls its corresponding httpPost Action on submit

I'm a little new to asp.net mvc and I have a question (very basic). I have hacked around but I am not totally sure about this and I could'nt find anything particularly helpful.
Assume that I have 2 controllers A and B and 2 views FullView and PartView
public class AController:...
{
//Renders FullView
public ActionResult Create
{
....
}
[HttpPost]
public ActionResult Create
{
....
}
}
public class BController:...
{
//Renders an Arbitrary partial View (PartView)
public ActionResult Create
{
....
}
//Saves the data of the partial View
[HttpPost]
public ActionResult Create
{
....
}
}
the 1st view (FullView) has the code
<%Html.RenderAction("Create", "B"); %>
my question is on submit will BController's action ([HttpPost] Create) run?
Thank you
That depends on what action you specify in your <form /> tag. This doesn't have anything to do with asp.net mvc. If you use Html.BeginForm() without parameters it will post to the current url (not the create action on BController).
Well 1st thing you could do is toggle some breakpoints in your actions and hit f5.
Second - what action is called purely depends on what url you hit with what http method.
But for your case, when you post form A and controller A processes post you might get into validation problems and that's when you return View() on a post action and that's why form B is rendered via its post method.