I created a simple todo list application in asp.net mvc 3 with web api and dbContext. ( with backbone and requirejs for the client )
Everything works fine, but i am sort of bothered with the fact that i have to sent the entire model to the server if i check or uncheck a todo item as done.
I would like to only sent the "done" field when submitting data.
I should mention that i'm also using the JsonNetFormatter to use JSON.NET as the default Serializer ( explained here: http://blogs.msdn.com/b/henrikn/archive/2012/02/18/using-json-net-with-asp-net-web-api.aspx ).
Currently this is my api controller method to update the model
public HttpResponseMessage Put(Todo todo)
{
_db.Entry(todo).State = EntityState.Modified;
_db.SaveChanges();
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
It takes this as json data
{"content":"Pick up milk","done":false,"id":10}
Off course this works, but it is updating the entire model, it should only update 1 field.
I can achieve to only send the changed fields to the server from the browser, but i am not sure what the web api method should look like.
I was thinking about doing something with FormCollection but this doesn't seem to work with the web api as it appears to be trying to serialize the submitted formvalues directly to FormCollection type, I get this error.
Cannot deserialize JSON object (i.e. {"name":"value"}) into type 'System.Web.Mvc.FormCollection'.
How can i send a partial update for 1 or more fields from a model to my web api ?
I only want to send the updated fields to the server, and from there only update those fields to the database. I certainly do not want to query the database before updating.
One approach would be to use a tool called Automapper and configure it so that null values don't overwrite existing ones when mapping Todo objects. For example:
Mapper.CreateMap<Todo,Todo>()
.ForMember(d => d.Id, o => o.Ignore())
.ForAllMembers(mo => mo.Condition(cond => !cond.IsSourceValueNull));
Then you would just have to map the received object value to the existing one, like this:
Mapper.Map(todo, item);
Another suggestion would be to use PATCH instead of PUT which is more appropriate to partial updates of resources according to REST.
You need to query the original object from the database, set its properties & call the _db.SaveChange()
public HttpResponseMessage Put(Todo todo){
var item = _db.Todo.First(i => i.id = todo.id);
item.Content = todo.Content;
item.Done = todo.Done;
_db.SaveChanges();
return new HttpResponseMessage<Todo>(HttpStatusCode.Accepted);
}
Ref.: http://msdn.microsoft.com/en-us/library/dd456854.aspx
Related
I am just trying to figure out what would be the easiest and quickest way to get particular value from WebApi controller.
my web API controller
public IEnumerable<string> Get()
{
return new string[] { fullname,lastname, email};
}
when I try to consume this web API in the angular controller by using the below method
this._httpService.get('/api/user').subscribe(values => {
this.details= values.json() as string[];
});
it returns all the values (fullname,lastname, email). but what I am trying to get here is lastname.
something like this.details.lastname
For clarity, if you are using Angular (not AngularJS), then the code is called a component not a controller.
Which version of Angular are you using? If it is > 4.3, then you don't need the .json() anymore. The mapping is handled for you automatically.
To answer your question ... the "quickest" way would be something like this (assuming Angular v6):
this.http.get<>('/api/user').subscribe(
details => this.lastName = details.lastName
);
Assuming that details is one item, not an array.
But this is definitely not the "best" way.
The best way would be to define an interface for your details and then build a proper client-side service to encapsulate your data access.
I have an api/v1/users/search uri within a Web API 2.2 project. This uri accepts a UserSearchRequest object. Here's a sample:
{
"DomainName":"ad.corp.domain",
"NetworkUserId":"jsmith2",
"FirstName":"John",
"LastName":"Smith"
}
The backend search logic will append all of the provided request parameter values to filter the set of users returned. Otherwise, an empty request object will result in all users being returned. However, if a client passes a request like the following then all users will be returned:
{
"UserName":"jsmith2"
}
In the example above, an invalid proprty of UserName was mistakenly used instead of NetworkUserId. However, instead of Web API returning an error, it simply ignored the additional property and returned all Users since no valid search criteria property values were provided.
What would be a proper way to validate the incoming request so that if an invalid prpoerty name is provided then Web API will return a 404 BadRequest, and preferably indicate the invalid property name?
You are facing The "Over-Posting" Problem. This can be handled in a couple of different ways :
Use the Bind attribute and whitelist or blacklist the properties you want.
public ActionResult search([Bind(Exclude="UserName")] Person person)
{
...
}
public ActionResult search([Bind(Include="DomainName, NetworkUserId,
FirstName, LastName")] Person person)
{
...
}
The other solution is to create a Custom Model Binder by extending IModelBinder. This will identify the extra columns and handle that as an error. You can check the implementation here.
In case of incorrect property names or datatypes, the web api will fail to deserialize the json to UserSearchRequest object and api controller parameter will be null. You can check for null which will indicate that input request was not properly formatted. Here is sample code:
public async Task<IActionResult> SearchUsers([FromBody] UserSearchRequest
searchRequest) {
if(searchRequest == null)
{
return BadRequest();
}
//For valid search request, continue search...
}
I think here you are getting the data from Particular x or y table. What you are showing here is returning directory data. It is actually easy part . Controller code you can manage validation. I think you are tying to get "NetworkUserId" but controller you might be changed to username . Please check your datatype . Please attach your controller code and model class.Thank you .
I have setup an OData v4 controller from an EF 6 model. All works fine when using the generated code.
On some objects that will be referenced in drop downs I want to limit the data sent so DTOs seem like the best approach. So I now have a controller the works fine with the normal object but as soon as I use a DTO on a custom route I run into trouble.
I've used the code below and it generated the DTO collection properly but when it returns the data I end up on the client with a 406 Not Acceptable status.
Any suggestions about what is going wrong?
As a side note, is my strategy of wanting to provide a full and a cut down object the best way to do it (so far as standards go)?
// GET: odata/AMO_GeneralStateTypes(5)/AMO_GeneralStates_Basic
[EnableQuery]
[Route("odata/AMO_GeneralStateTypes({key})/AMO_GeneralStates_Basic")]
public IQueryable<AMO_GeneralStatesDTO> GetAMO_GeneralStates_Basic([FromODataUri] int key)
{
return db.AMO_GeneralStateTypes
.Where(m => m.StateTypeId == key)
.SelectMany(m => m.AMO_GeneralStates.Select(n => new AMO_GeneralStatesDTO()
{
StateId = n.StateId,
StateGuid = n.StateGuid,
ParentStateId = n.ParentStateId,
State = n.State,
TypeId = n.TypeId,
SortOrder = n.SortOrder,
Enabled = n.Enabled
}));
}
For 406 Not Acceptable, please make sure the return type is defined in the finally Edm Model. That is, AMO_GeneralStatesDTO is type that Edm model can serialize.
BTW, For Web API OData,
[Route("odata/AMO_GeneralStateTypes({key})/AMO_GeneralStates_Basic")]
should be
[ODataRoute("odata/AMO_GeneralStateTypes({key})/AMO_GeneralStates_Basic")]
I have met some problems like this in odata webapi for dto
here is my solution: you need to register your entitytype for odata or it will come a 406 error
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntityType<YourDTOType>();
Hope it's helpful
I'm trying to make odata rest-based service calls through a URI.
I have an ADO.NET Entity data model that is mapped to one of the tables on the SQL back end. Which created the necessary edmx file under my "Models" folder
I then created a controller labeled spcontrol using the "Web API 2 Odata Controller with actions, using Entity Frameowrk" option. I then set the model class and Data context class and the visual studio scaffolding did the rest. The first few lines of the controller look like this...
public class spcontrolController : ODataController
{
private Entities db = new Entities();
// GET odata/spcontrol
[Queryable]
public IQueryable<Database_table_Name> Getspcontrol()
{
return db.Database_table_Name;
}
// GET odata/spcontrol(5)
[Queryable]
public SingleResult<Database_table_Name> GetDatabase_table_Name([FromODataUri] int key)
{
return SingleResult.Create(db.Database_table_Name.Where(Database_table_Name=> Database_table_Name.ID == key));
}
and then it goes on for the PUT, POST, PATCH, DELETE methods. In my WebApiConfig.cs file, I set the following lines
using System.Web.Http.OData.Builder;
using resttest.Models;
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Database_table_Name>("spcontrol");
config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());
Finally, I'm trying to query the table with odata. So my url looks something along the lines of
http://testsite:8012/odata/spcontrol?$top=5
However, this call does not return any data, but only returns the following text
{
"odata.metadata":"http://localhost:33421/odata/$metadata#spcontrol","value":[
]
}
How can I get the actual values within the SQL table? What am I doing wrong?
Looks like it is working fine - you just have no data to return so array in response payload is empty. Add some data and hit the endpoint again.
Run a profiler on your database to see what commands are being sent from your service to check correct tables are being used etc
Assume i have this model
public partial class Todo
{
public int id { get; set; }
public string content { get; set; }
public bool done { get; set; }
}
And i send this as json data to my controller as a patch request.
This is mearly the action of toggeling a checkbox.
I think it makes sence that i only want to sent that to my server, and not the entire model.
{ "id":1, "done" : true }
What does my WebApi controller need to look like in order to correctly process this, simple, json patch request ? Should i be using web api for this, or should i use a more rpc styled approach with mvc ?
It seems like a very basic thing to do, but i can't seem to get it right !
I think i might need to use a different parameter in my controller method, but i'm not sure.
Thank you for your time.
You can find PATCH feature in the OData pre-release Nuget package: Microsoft.AspNet.WebApi.OData.
Information how you can use it to create an action for handling PATCH can be found in the Partial Updates (PATCH requests) section of the blog post about OData support in ASP.NET Web API.
Changing the method to PATCH doesn't change Web API behaviour in any way. There is no built in mechanism for doing partial updates. One of the reasons there was no PATCH method for so long is that there is no ubiquitous media type for applying patches to resources.
Secondly, you are asking Web API to do object serialization for you so there just is no such concept of applying a partially updated object. There would be so many conventions to agree on, what does a null value mean, what about an empty value, how do I say "don't update this DateTime". What about related objects, child items? How do you cause a child item to be deleted? Unless the CLR team implements some concept of a type that only contains a subset of members from another type, partial updates and object serialization are not going to go well together.
Aliostad mentions UpdateModel and that is possible when updating from a HTML form because the media type application/x-www-form-urlencoded explicitly allows for an arbitrary set of name value pairs. There is no "object serialization" going on. It is just a match of names from the form being matched to names on the Model object.
For myself, I created a new media type I use to do partial updates that works like a form but is more advanced in that it can handle hierarchial data and it maintains an order to the updates.
ASP.NET Web API seems to be missing UpdateModel, TryUpdateModel, etc.
In ASP.NET MVC, you could use them to achieve the desired effect. I have created a work item in ASP.NET Web Stack which you can vote for and if it gets enough votes, it will be implemented.
I used Microsoft.AspNet.WebApi.OData for my project and I had some problems working with JSON (working with numbers in my case). Also, the OData package has some dependencies which, from my point of view, are too big for a single feature (~7MB with all dependecies).
So I developed a simple library which do what you are asking for: SimplePatch.
How to use
Install the package using:
Install-Package SimplePatch
Then in your controller:
[HttpPatch]
public IHttpActionResult PatchOne(Delta<Todo> todo)
{
if (todo.TryGetPropertyValue(nameof(Todo.id), out int id)) {
// Entity to update (from your datasource)
var todoToPatch = Todos.FirstOrDefault(x => x.id == id);
if (todoToPatch == null) return BadRequest("Todo not found");
todo.Patch(todoToPatch);
// Now todoToPatch is updated with new values
} else {
return BadRequest();
}
return Ok();
}
The library support massive patch too:
[HttpPatch]
public IHttpActionResult PatchMultiple(DeltaCollection<Todo> todos)
{
foreach (var todo in todos)
{
if (todo.TryGetPropertyValue(nameof(Todo.id), out int id))
{
// Entity to update (from your datasource)
var entityToPatch = Todos.FirstOrDefault(x => x.id == Convert.ToInt32(id));
if (entityToPatch == null) return BadRequest("Todo not found (Id = " + id + ")");
person.Patch(entityToPatch);
}
else
{
return BadRequest("Id property not found for a todo");
}
}
return Ok();
}
If you use Entity Framework, you have to add only two lines of code after the call to the Patch method:
entity.Patch(entityToPatch);
dbContext.Entry(entityToPatch).State = EntityState.Modified;
dbContext.SaveChanges();
Furthermore, you can exclude some properties to be updated when the Patch method is called.
Global.asax or Startup.cs
DeltaConfig.Init((cfg) =>
{
cfg.ExcludeProperties<Todo>(x => x.id);
});
This is usefull when you are working with an entity and you don't want to create a model.