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.
Related
Preface
I recently was assigned a project for college, were using ASP.NET Core 6 Web API, and Entity Framework. Due to the subject at hand the objective is to implement an API without taking any consideration around the structure and routing of it.
So this is a warning: I did not choose the routing nor can I change it, even if slightly.
The actual problem
I have 3 model classes, let's call them Application, Module and Data. A single application has various child modules, while each module has a series of data children.
The issue is that when I want to create a Module or a Data model, the teachers decided to give it a twist and we're forced to take data from the body and the URL.
So that everyone is on the same page ill give an example of how to create a Module.
The url goes like this
POST: https://ip:port/api/OurProjectName/{application}/
POSTBODY:
<?xml version="1.0"?>
<Module><!--here on the root node i can be a bit malleable and name it whatever i want so no issues with DTO's-->
<id>99999</id>
<name>some name</name>
<creation_dt>{the current timestamp}</creation_dt>
</Module>
If the implementation was correct what it would do is go to the controller grab the application string and the Module/ModuleDTO, fetch an Application model from the database where the application string is equal to the field name.
Then inside that function due to our choice of using EF, we would just grab the module sent in the body(or create one in case of a DTO) and associate the respective parent sent in the route parameters.
My failed attempts
Although I talked about modules, the behaviour of data is nearly the same as modules except the teachers decided that for some reason with data you can only create and delete, and you can't list, update, etc... When it comes to posting instead of
POST: https://ip:port/api/OurProjectName/{application}/
I have to do something like
POST: https://ip:port/api/OurProjectName/{application}/{module}
while application this time only serves for checking past errors due to the way the database was structured.
I firstly attempted to do something like
[HttpPost("{application}/{module}")]
[Produces("application/xml")]
[Consumes("application/xml")]public IActionResult Post(string application, string module, [FromBody] Data data)
{
var mod = _context.Modules.SingleOrDefault(m => m.name == module);
if (mod == null)
{
return NotFound();
}
if (mod.parent.name != application)
{
//... this is irrelevant for now
}
data.parent = mod;
// TODO: dereference here... fix later
mod.datas.Add(data);
_context.Add(data);
_context.SaveChanges();
return Ok(data);
}
But it returned the usual error of 415, although this try was mostly hoping that [FromBody] wouldn't complain at the routing as I've had good results with put (although in that situation it kinda follows convention instead of completely breaking it) and to top it off since Data has a navigation field, swagger will iterate the objects until no parents are found so the xml object in the body looked... well like a configuration file.
I then tried to use decorations around the two strings [FromRoute], but it still wouldn't budge, and got an "unsupported media format".
Then I tried to do some custom manual routing (as in no decorations)
here's my startup file that I created with only that purpose in mind (since I otherwise would expect the Program.cs file to get cluttered which is a thing I hate)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI();
}
var option = new RewriteOptions();
option.AddRedirect("^$", "swagger");
app.UseRewriter(option);
app.UseCors("AllowAll");
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
endpoints.MapControllerRoute(
name: "postDataCustom",
pattern: "api/OurProjectName/{application}/{module}", new {controller = "DataController", action = "Post"});
});
}
I removed the [HttpPost(etc)] and expected to hopefully fix it, but when I checked swagger not only was it missing but the endpoint didn't even exist (I tested with Postman).
I commented it out (the part where I manually set a ControllerRoute) and found some other posts around the subject.
And I saw that I could actually do some kind of dto and put the decorations in each field, this was game breaking as I knew then I would be able to just place a single variable inside the controller function and avoid [FromData] to go insane and parse properly the stuff.
public IActionResult Post(DataDTO data)
{
var mod = _context.Modules.SingleOrDefault(m => m.name == data.module);
if (mod == null)
{
return NotFound();
}
Console.WriteLine(mod.name);
// ...(if it wrote to console I could progress)
return Ok();
}
public class DataDTO
{
[FromRoute]
public string application { get; set; }
[FromRoute]
public string module { get; set; }
[FromBody]
public Data data { get; set; }
public DataDTO(string application, string module, Data data)
{
this.application = application;
this.module = module;
this.data = data;
}
}
It still gave me the error code, this time since I was a bit more skeptical of such a failure, I made a much simpler DTO and routing just to check if I could at least get a 200 code and I did it, the problem is [FromRoute] is pretty much "ignored"
[HttpPost("{module}")]
public IActionResult Post(testeDTO data)
{
Console.WriteLine(data.module);
Console.WriteLine(data.bb);
return Ok();
}
public class testeDTO
{
[FromRoute]
public string? module { get; set; }
[FromBody]
public string bb { get; set; }
public testeDTO(string bb)
{
this.bb = bb;
}
}
Although the route is there and works fine it never assigns the value to module, it seems like whatever is on the body overwrites it (I tried to get around not having duplicate fields by adding the possibility of module being null so I wouldn't be forced to send a module in the body, but no dice, I can't decouple both behaviors).
At this point I'm kinda out of ideas, it's even more frustrating when old documentation gets in the way with search results.
I've also considered using x-www-urlencode, although I'm not sure if it's an option I can take as again the requirements are very restrictive.
TL;DR I can't get a post with parameters in the route to hit successfully the action Post...
Update as of 26/12/2022:
I was playing around the code and weirdly this version "works" i tested it with swagger and it seems to not produce any error, i even checked git to see if added or removed some kind of config or something of the kind but since the last commit(which was uncharacteristic of me as it was a "backup" of sorts) these were the only things that changed
[HttpPost("{module}")]
public IActionResult Post([FromRoute]string module, [FromBody]testeDTO data){
Console.WriteLine(module);
Console.WriteLine(data.bb);
return Ok();
}
...
public class testeDTO{
[FromBody]
public string bb{get;set;}
public testeDTO(string bb){
this.bb = bb;
}
}
I will try to now play around the code to see if i can get to the solution
Step 1)
Go back to your first attempt:
[HttpPost("{application}/{module}")]
[Produces("application/xml")]
[Consumes("application/xml")]
public IActionResult Post(string application, string module, [FromBody] Data data)
{
// Your code
}
Step 2)
Add this line to your Program.cs:
builder.Services.AddControllers().AddXmlSerializerFormatters();
or this line to your ConfigureServices method:
services.AddControllers().AddXmlSerializerFormatters();
Step 3)
Rework your XML to match the parameter name:
<?xml version="1.0"?>
<Data>
<id>99999</id>
<name>some name</name>
<creation_dt>{the current timestamp}</creation_dt>
</Data>
Things should then work.
Step 2) is key, listen to what your being told - 415 is unsupported media type (which you know). So support the media type. I don't know why you think it shouldn't be possible to use a route parameter and a body parameter in the same endpoint but it is.
You may also not want to mix attribute routing and configuration level routing.
I found the answer, apparently the issue revolved around
[FromBody]
//... and
[Consumes("application/xml")]
As the edit on my question suggested i removed the consumes decoration and it started working unfortunately as suggested by the question i cant either change the routing nor can i change the media type it produces and consumes, so my idea was at the time "if i cant use body decorations ill just manually parse the body and keep the routing decorations".
And thats when it hit me XMLserializer requires at least one parameterless constructor to work, and since i had placed parameterless constructors on all other models except on this one, it tricked me into thinking the issue was around the decorators. Ill leave some resources that helped me get into the solution
How to parse xml into objects
Request params documentation
I am developing an MVC 5 app using EF 6 database first approach. I have a certain validation which is required for fields in many of my models. I am using remote validation for validating them. Since it was being used in a lot of models so I am trying to go for a generic method.
For that matter I made an Interface named IEntity which includes all properties being used in my models. Then I did the following for my Validation method:
[HttpPost]
public JsonResult UniqueCheck<T>(T code) where T : class, IEntity
{
MyEntities db = new MyEntities();
if (db.Set<T>().Any(x=>x.S1 == code.S1))
{
return Json("Already Exists!");
}
else
{
return Json(true);
}
}
And following is how I am calling the validation on properties in models:
[Remote("UniqueCheck", "Rules", HttpMethod = "POST")]
public string S1 { get; set; }
But the problem is that the validation is not working and when I checked in the browser console I got that the validation is actually going into the method but there was a 500(Internal Server Error) returned.
I know that the problem is with T written with method name because when I removed the generics and hardcoded my model name, it works fine.
I only want to use MVC's remote validation and I would be very happy to get this generic method working because otherwise it would be a copy/paste on a lot of locations.
Is there a way to specify the success return code for a method in Web API controller?
My initial controller was structured like below
public HttpResponseMessage PostProduct(string id, Product product)
{
var product= service.CreateProduct(product);
return Request.CreateResponse(HttpStatusCode.Created, product);
}
However, there is drawback to the above approach when you generate Web API help pages. The Web API Help page API cannot automatically decode that the strongly typed Product is the response and hence generate a sample response object in its documentation.
So I go with the below approach, but here the success code is OK (200) and not Created (201). Anyway I can control the success code of the method using some attribute style syntax? Plus, I would also like to set the Location header to the URL where the created resource is available - again, this was easy to do when I was dealing with HttpResponseMesage.
public Product PostProduct(string id, Product product)
{
var product= service.CreateProduct(product);
return product;
}
Regarding your observation below:
However, there is drawback to the above approach when you generate Web API help pages. The Web API Help page API cannot automatically decode that the strongly typed Product is the response and hence generate a sample response object in its documentation.
You can take a look at HelpPageConfig.cs file that gets installed with HelpPage package. It has an example exactly for a scenario like yours where you can set the actual type of the response.
In latest version (5.0 - currently RC) of Web API we have introduced an attribute called ResponseType which you can use to decorate the action with the actual type. You would be able to use this attribute for your scenario.
I do this:
[HttpGet]
public MyObject MyMethod()
{
try
{
return mysService.GetMyObject()
}
catch (SomeException)
{
throw new HttpResponseException(
new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content =
new StringContent("Something went wrong.")
});
}
}
If you don't get what you expected, throw a HttpResponseException.
I currently have a MVC 3 Web Application with around 50 Views.
Is it somehow possible to provide some kind of API next to my Views so everything still works fine and dandy?
My kinda perfect world idea:
Server is running and every request gets to its view. The user can decide in his get request if he wants the raw data in lets say JSON. Additional to the raw data also the Structure/Names of the Viewmodel will be parsed so the Modelbinder can do his stuff.
or another approach might be to somehow deactivate my Views to have only the API available (again with raw data and structure).
Is this even possible (the modelbinder is kinda a big concern) and maybe even without much effort/trouble to do?
If you don't want to do everything all over again with WebAPI, you can implement some ActionFilterAttribute to change the ActionResult based on some querystring f.i.
Something like this:
public class ResultSwitcherAttribute: ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.HttpContext.Request["data"] != null && filterContext.HttpContext.Request["data"] == "json")
{
filterContext.Result = new JsonResult
{
Data = (filterContext.Result as ViewResult).Model,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
}
Then, you'll register that either in application_start or per controller/action like this:
[ResultSwitcherAttribute]
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new TestModel()
{
Web = "http://www.mywebpage.com", Name = "Yngve"
});
}
}
When you now access your url with ?data=json you'll get a JSON-representation of the Model, instead of the view.
EDIT: Code sample updated
Consider adding an ASP.NET Web API to your app. It's extremely simple to implement, entailing little more than adding an API controller with methods corresponding to your REST actions. I don't know how one would go about adding this to an MVC 3 app, but I have a recipe for MVC 4 Internet Applications in this stackoverflow question. So, if you're fine with upgrading your project so that it is Web API capable, perhaps to MVC 4, I think it'd represent a good alternative.
To learn ASP.NET Web API, this tutorial is a good place to start.
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