REST API routing standards for complex objects - asp.net-web-api

For example, lets say I have the following model that is used to update a person's details via Web API:
public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Are there any standards that dictate how I should structure the API routes for this endpoint when posting this data from the browser?
Would I need to have:
[Route("people/person/{personId}")]
[HttpPost]
public IHttpActionResult SavePerson(int personId, Person personDetails)
Or should I just use:
[Route("people/person")]
[HttpPost]
public IHttpActionResult SavePerson(Person personDetails)
If PersonId is 0 then it is assumed that this is new data and hence a new record will be created, otherwise an update will be performed.

BAsed on my experience the best method to post (or PUt) complex object is:
[Route("people/person")]
[HttpPost]
public IHttpActionResult SavePerson([FromBody]Person personDetails){
// then you can check here if model is valid and if id is ont set ..then call insert CRUD method or UPDATE method..something like:
if(!ModelState.IsValid) return BadRequest(ModelState);
return Ok(personDetails.Id == 0 ? _repository.insert(personDetails) : _repository.update(personDetails));
}
Hope it Help you ..
p.s other REST method for what you described is PATCH

It's not required specify the '[FromBody]' in post action. Rest api send's the data in form body only,if it is complex object.
[Route("people/person")]
[HttpPost]
public IHttpActionResult SavePerson(Person personDetails){
// then you can check here if model is valid and if id is ont set ..then call insert CRUD method or UPDATE method..something like:
if(!ModelState.IsValid) return BadRequest(ModelState);
return Ok(personDetails.Id == 0 ? _repository.insert(personDetails) : _repository.update(personDetails));
}

Related

ASP.NET Web API - how to pass unknown number of form-encoded POST values

The front-end of my application can send unknown number of POST values inside a form. Fro example in some cases there will be 3 values coming from certain textboxes, in some cases there will be 6 values coming from textboxes, dropdowns etc. The backend is ASP.NET Web API. I know that a simple .NET value can be passed in URI parameter to a "POST Action" using FromURI attribute and a complex type can be passed in body and fetched using FromBody attribute, in any POST Action. But in my case the number of form data values will NOT be constant rather variable and I can't use a pre-defined class to hold values using 'FromBody' attribute.
How can I tackle this situation?
You can use the FormDataCollection from the System.Net.Http.Formatting namespace.
public class ApiFormsController : ApiController
{
[HttpPost]
public IHttpActionResult PostForm(FormDataCollection form)
{
NameValueCollection items = form.ReadAsNameValueCollection();
foreach (string key in items.AllKeys)
{
string name = key;
string val = items[key];
}
return Ok();
}
}
Try to send this properties as list of properties. Make model something like this:
public class PostModel
{
public IEnumerable<PropertyModel> Properties { get; set; }
}
public class PropertyModel
{
public string Value { get; set; }
public string Source { get; set; }
// etc.
}
And action:
public IHttpActionResult Post(PostModel model)
{
//Omited
return Ok();
}

Filtering queries across $expands with WebApi2 OData

I have a WebApi 2.1 OData (v 5.1.1) service backed in Entity Framework 6.1. I'm trying to lock it down from a security standpoint, so that users can only query data that is theirs. I have everything working fine, until you get to the $expands option.
For the sake of this discussion, consider the following simplified data model:
public class Scenario
{
public Guid Id { get; set; }
public Guid CreatedById { get; set; }
}
public class Property
{
public Guid Id { get; set }
public Guid CreatedById { get; set; }
public IQueryable<Scenario> Scenarios { get; set; }
}
When I call /Properties(guid'SOMEGUID')?$expand=Scenarios, I need to be able to make sure that only Scenarios where the CreatedById = CurrentUserId are returned. This needs to happen on the server-side and not in the client-side query.
WCF Data Services had QueryInterceptors that would handle this kind of situation... what is the equivalent in WebApi 2.1 OData?
Thanks!
here's a gist with a sample on how can you implement this on your own:
https://gist.github.com/anonymous/9237151
Based on my git, you can use a similar validator and implement your validation logic on a CanAcess method or similar. Let me know if this helps you.
We will have soon an official sample on http://aspnet.codeplex.com
There are two ways to solve your problem if I understood your question correctly.
Call the ApplyTo method of ODataQueryOptions on the IQueryable result
public IQueryable<Property> Get(ODataQueryOptions queryOptions)
{
....
return queryOptions.ApplyTo(properties);
}
Add attribute Queryable on the GetData method and let WebAPI handles the query option
[Queryable]
public IQueryable<Property> Get()
{
...
return properties;
}

Passing ViewModel to Web-Api action

Is it possible to pass an ViewModel object to WebApi controller action instead of separate params?
Instead of using:
public class ContactsController : ApiController
{
public IEnumerable<Contact> GetContacts(string p1, string p2)
{
// some logic
}
}
I would like to use:
public class ContactsController : ApiController
{
public IEnumerable<Contact> GetContacts(TestVM testVM)
{
// some logic
}
}
public class TestVM
{
public string P1 { get; set; }
public string P2 { get; set; }
}
This doesn't seem to work for me. When I call /api/contacts/?P1=aaa&P2=bbb the testVM object doesn't get populated (null).
Also, I would like the TestVM to have valdiation attribtues defined and use ModelState.IsValid in my API controller.
Unless told otherwise WebApi will deserialise complex models using the content/body of the request. To tell WebApi to use the Url to construct the model you need to specify the [FromUri] attribute:
public IEnumerable<Contact> GetContacts([FromUri]TestVM testVM)
{
// some logic
}
I know it's kind of late to post another answer but I thought it could be useful for anyone who uses .net core as a web API service
public IEnumerable<Contact> GetContacts([FromQuery]TestVM testVM)

webapi action overload

here are my actions
public AddressModel[] Get()
{
return addresses.ToArray();
}
public AddressModel Get([FromUri]GetAddressModelById model)
{
return Addresses.FirstOrDefault(x => x.Id == model.Id);
}
...
public class GetAddressModelById
{
public Guid Id { get; set; }
}
the urls looks like this
domain:port/api/controller
domain:port/api/controller/[guid]
and the routing is the default routing.
When I run this i get the exception Multiple actions were found that match the request. what am I missing for this to work?
Try domain:port/api/controller/model=[guid]
It would also be good if you could do:
public AddressModel Get([FromUri]Guid model)
{
...
}
The reason you're getting this error is because if you request for domain:port/api/controller, we cannot decide whether to dispatch it to Get() or Get([FromUri]GetAddressModelById model) with model set to null. You have following two options in this case
Use plain Guid type parameter instead of the class wrapper
Post your complex type in body instead of sending it through URI

MVC ASP.NET MVC3 AllowHtml Attribute Not Working?

The question is very simple:
Say you have a model called Person
public class Person
{
public int PersonID {get; set;}
public string Name {get; set;}
[AllowHtml] // Allow html in Intro property
public string Intro {get; set;}
[ScaffoldColumn(false)]
public string ComplicatedValue {get; set;}
}
In controller's Create action
[HttpPost]
public ActionResult Create(Person o, FormCollection collection)
{
// whatever code here;
}
If you run it,
input plain text for Intro, no
problem happens.
input html content for Intro, no matter how you set
your configuration file, it will
tells "A potential dangerous ..."
I DO find the reason of this problem.
If you change the function to
public ActionResult Create(Person o) // Get rid of the *FormCollection collection*
{
// whatever code here;
}
This will eliminate the "potential dangerous" error.
But my problem is that for my application, I have to use the secondary parameter FormCollection collection in the Create Action method, because I need to use some other control values and server variable to assign a calculated value to the ComplicatedValue property.
If any expert of ASP.NET MVC3 have met the same problem as me, and found a solution, please kindly let me know.
This forum at this link discusses this issue at length and gives some workarounds.
http://forums.asp.net/p/1621677/4161637.aspx
Here is one solution from that thread that may or may not work for you:
public ActionResult Create(Person o) // Get rid of the *FormCollection collection*
{
FormCollection form = new FormCollection(Request.Unvalidated().Form);
// whatever code here;
}
or my own recommendation:
public ActionResult Create(Person o, int otherControlValue1, int otherControlValue2, ...)
{
o.ComplicatedValue = CalculateComplicatedValue(otherControlValue1, otherControlValue2, ...);
// whatever code here.
}
In my case, I am not using the FormCollection, but it was there so that I had a different footprint on my [HttpPost] method. I did this hack and put in a bogus parameter:
public virtual ActionResult Edit(int id)
{
return View(this.repository.GetById(id));
}
[HttpPost]
public virtual ActionResult Edit(int id, int? bogusID)
{
var d = repository.GetById(id);
if (TryUpdateModel(d))
{
repository.Save();
return RedirectToAction("Index");
}
return View();
}
Might I suggest using a custom model binder instead of pulling the complex data from a FormCollection. Scott Hanselman has a blog post on creating a custom model binder that would serve as a good template. In his post he puts together a DateTimeModelBinder that allows a DateTime property to be set either by a single input containing the date or a pair of inputs containing a date and a time.
have you tried
[Bind(Exclude="ComplicatedValue")]
:
[HttpPost]
public ActionResult Create([Bind(Exclude="ComplicatedValue")]Person o)
{
}
?
with that it allows you to exclude setting ComplicatedValue property on the form and still submit the object as a Person class.
hope that helps

Resources