Post or Put multiple items in a single request - asp.net-web-api

Is it possible to Post or Put more than a single item at a time, in a single request?
From
GET /api/books Get all books.
POST /api/books Create a new book.
PUT /api/books/{id} Update an existing book.
To
POST /api/books Create books.
PUT /api/books Update books.

Let's say you have a class called Book, defined as:
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
}
Now we have a simple Web.Api controller with a POST method:
public class BooksController : ApiController
{
[HttpPost]
[Route("books")]
public HttpResponseMessage PostBooks([FromBody] IEnumerable<Book> books)
{
return Request.CreateResponse(HttpStatusCode.Created);
}
}
The method doesn't do anything with the data, it's purely so it compiles and runs. My first attribute indicates this is a POST, the second attribute determines the route, I'm using attribute based routing because I just like it more, but you can roll your own routing rules.
Now the PostBooks method takes in a parameter of type Ienumerable<Book>, not just a single parameter of type Book.
When I now stand up this little endpoint, and hit it with the following url:
http://localhost:port/books
And specify the request to be a POST and supply the following payload in the body of the request:
[
{
"Title":"This is a book",
"Author":"Joe Bloggs"
},
{
"Title":"This is a book: The reckoning",
"Author":"Joe Bloggs"
}
]
My breakpoint is hit and Web.API managed to deserialize my payload into the books parameter when it comes into the PostBooks method:
The same applies for a PUT, the only thing that needs changing is the attribute.

Finally I use like below's code.
// Post Multi
// POST: api/books
[Route("api/books")]
[ResponseType(typeof(IEnumerable<Book>))]
public IHttpActionResult Postbooks(IEnumerable<Book> books)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
foreach (var item in books)
{
db.Book.Add(item);
}
try
{
db.SaveChanges();
}
catch (DbUpdateException)
{
throw;
}
return Ok(books);
}

Related

DataSourceRequest is not deserializing for a WebAPI Get method

I am trying to call a WebAPI method from Angular 5 like this:
selectClaims(state: DataSourceRequestState):Observable<DataResult>
{
return this.http.get<GridDataResult>(`${this.apiUrl}/SelectClaims?${toDataSourceRequestString(state)}`);
}
Which calls the API method as expected. The API method is:
[Route("SelectClaims")]
[HttpGet]
public IHttpActionResult SelectClaims([FromUri][DataSourceRequest]DataSourceRequest ClaimsRequest)
{
if(ClaimsRequest == null)
ClaimsRequest=new DataSourceRequest { Page=1, PageSize=20 };
var result = _db.Claims.ToDataSourceResult(ClaimsRequest, c => { c.SortHistory(); return c; });
return Ok(result);
}
The trouble is that ClaimsRequest only de-serializes Page and PageSize correctly. Filters and Sorts don't come through:
Fiddler tells me that the URL from Angular is:
GET /api/v1/Claims/SelectClaims?filter=id~eq~2&page=1&sort=firstName-asc&pageSize=20 HTTP/1.1, but in the controller both filter and sort are null.
If I create a URL through Swagger like: 'http://localhost:50223/api/v1/Claims/SelectClaims?ClaimsRequest.page=1&ClaimsRequest.pageSize=11&ClaimsRequest.sorts=firstName-desc' I do see a sort array in the API method, but the "Member" field is null.
Any attempt to add a filter through Swagger like 'http://localhost:50223/api/v1/Claims/SelectClaims?ClaimsRequest.page=1&ClaimsRequest.pageSize=11&ClaimsRequest.filters=id~eq~2' results in a "Cannot create an instance of an interface." error.
The state is a DataSourceRequestState in the angular component from a Kendo Grid for Angular.
I have simulated this in a simple test program and everything works fine there. The only difference in my test program is that the API controller targets .Net Core and the real system targets .Net 4.6.1.
Do I have to de-serialize manually in .Net 4.6.1 for some reason, or is something else going on here?
It should be a POST not a GET. Something like this:
return this.http.post<GridDataResult>(`${this.apiUrl}/SelectClaims`, toDataSourceRequestString(state)});
I needed it to be a GET (URL) so i created a new object
public class GridParamaterBinder
{
public int Page { get; set; }
public int PageSize { get; set; }
public string Filter { get; set; }
public string Sort { get; set; }
public DataSourceRequest ToDataSourceRequest(IConfigurationProvider mapper, Func<string, string> OverDefaultParamaterMapping)
{
DataSourceRequest result = new DataSourceRequest();
result.Page = Page;
result.PageSize = PageSize;
result.Sorts = GridDescriptorSerializer.Deserialize<SortDescriptor>(Sort);
result.Filters = FilterDescriptorFactory.Create(Filter);
return result;
}
}
and used it instead of the Telerik effort.
in API I Bind it like so
public virtual DataSourceResult Get([FromUri]GridParamaterBinder request)
And then used it like
DataSourceResult results = query.ToDataSourceResult(request.ToDataSourceRequest(), r => (r)));
Thanks #KevDevMan for your solution. I found this example,
then I changed my API controller like this and it worked like a charm :
[HttpGet, Route("for-kendo-grid")]
public DataSourceResult GetProducts([System.Web.Http.ModelBinding.ModelBinder(typeof(WebApiDataSourceRequestModelBinder))] DataSourceRequest request)
explanation here

C# Web API 405 method not routing to my PUT, always hitting my GET

I have been working with Web API for well over a year, and haven't run into this problem before. I am really at my witt's end after spending hours googling, and looking at stack overflow posts etc. I am wondering if I just have some brain fart thing going on.
I have a controller where I want a get and a put against the same route:
[Route("api/strings")]
[HttpGet]
public IHttpActionResult GetStrings()
{
try
{
var siteStrings = svc.GetSiteStrings(_appId);
return Ok(new { strings = siteStrings });
}
catch(Exception)
{
return InternalServerError();
}
}
[HttpPut]
[AcceptVerbs("PUT")]
[Route("api/strings")]
public IHttpActionResult PutString(String key, String text)
{
//TODO: add authorization to this one.
try
{
if (svc.UpdateString(key, text, _appId))
{
return Ok();
}
return InternalServerError();
}
catch (Exception)
{
return InternalServerError();
}
}
My routing is just the default out of the box routing as can be seen here:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
After a seeming eternity doing configs over and over based on so many other stack overflow questions about 405 errors, I realized that I can verify that mine is trying to route my PUT method to my GET's endpoint. However if I change the route on my put to something else, I always get a 404. I can't figure out why it is putting all my puts or posts through to my GET verb and thus saying that the action is not allowed on my route. I am sure I am just missing something trivial at this point but mental fatigue and tired eyes are just holding me back. Anyone see something stupid I am doing wrong?
All I had to do was create a class that my put has to model bind to rather than two parameters, and the routing started working. That is surprising, but I just had to do the following and it works:
public class temp
{
public String key { get; set; }
public String text { get; set; }
}
[HttpPut]
[AcceptVerbs("PUT")]
[Route("api/strings/")]
public IHttpActionResult PutString(temp data)
{
//TODO: add authorization to this one.
try
{
if (svc.UpdateString(data.key, data.text, _appId))
{
return Ok();
}
return InternalServerError();
}
catch (Exception)
{
return InternalServerError();
}
}
Obviously I won't keep that temp class there, but it was a quick stab in the dark to see if the put would route correctly that way. Sure enough it did. Must be something in the model binding specs for Web API that I wasn't aware of.

How To Pass formdata parameters into ASP.NET WebAPI without creating a record structure

I have data coming into my form that looks like the image below (sessionsId: 1367,1368).
I've create c# in my webapi controller that works as below. when I've tried ot just make use SessionIds as the parameter (or sessionIds) by saying something like PostChargeForSessions(string SessionIds) either null gets passed in or I get a 404.
What is the proper way to catch a form parameter like in my request without declaring a structure.
(the code below works, but I'm not happy with it)
public class ChargeForSessionRec
{
public string SessionIds { get; set; }
}
[HttpPost]
[ActionName("ChargeForSessions")]
public HttpResponseMessage PostChargeForSessions(ChargeForSessionRec rec)
{
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, new ShirtSizeReturn()
{
Success = true,
//Data = shirtSizeRecs
});
return response;
}
You can declare the action method like this.
public HttpResponseMessage Post(string[] sessionIds) { }
If you don't want to define a class, the above code is the way to go. Having said that, the above code will not work with the request body you have. It must be like this.
=1381&=1380

How to add a bind list to the TryUpdateModel in asp.net mvc3

I have the following action method:-
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
Assessment a = elearningrepository.GetAssessment(id);
try
{
if (TryUpdateModel(a))
{
elearningrepository.Save();
return RedirectToAction("Details", new { id = a.AssessmentID });
}
}
//code does here
but I can not write something like if (TryUpdateModel(a, "Assessment", new string { "Date"})) to specify that I only allow the Date property to be updated.
So how I can add a bind list to the above if (TryUpdateModel(a))?
BR
but I can not write something like
if (TryUpdateModel(a, "Assessment", new string { "Date"}))
That's because you should write it like this, since the allowed properties argument represents a string array:
if (TryUpdateModel(a, "Assessment", new[] { "Date" }))
{
}
I would suggest that you stay away from using TryUpdateModel in general.
The repository usually has an update method that sets the entityState to modified before Save() is called, i cannot see that in the code above.
If your goal is to display a record and only allow date to be saved, then create a view for that model, and render fields with:
This sets the model for the view:
#model YourNamespace.Models.Assessment
#Html.DisplayFor(model=>model.propertyToDisplay)
on the items you only want to display, and a
#Html.EditorFor(model=>model.Date)
In your action controller you take the properties you want to bind to as input parameters:
Edited
class Assessment
{
public int Id { get; set; }
public DateTime Date { get; set; }
//Other properties
}
public ActionResult Edit(int Id, DateTime Date)
{
var assessment = elearningrepository.GetAssessment(id);
assessment.Date = Date;
elearningrepository.UpdateAssessment(assessment);
elearningrepository.Save();
//Redirect to action Detail
}
In this case the model binder should just bind to Id, and Date, so even if someone tries to post other values (editing the html form is easy), parameters in ActionResult should be named exactly as in the Model and use that to fetch and update the entity.
You should validate that the user actually can access and edit that id, or as an alternative use MVC Security Codeplex to check that the Id parameter has not been tampered with. it is really easy and convenient to use, but that is another discussion.
As an alternative you can use an attribute like this, described in this blog, but I don't use that myself:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create( [Bind(Include="Id,Date")] Assessment assessment)
i tried this an it works fine
string[] allowedProperties = new[] { "Date" };
try
{
if (TryUpdateModel(a, allowedProperties))
{

ASP MVC 3 testing controller calling ModelState.IsValid always returns true

I have an ASP MVC 3 application and in my Model I have implemented IValidatableObject.
When my controller posts for a create or edit, I obviously only want to save the model if it is valid.
I see many blogs and posts and answers that say something like
if(!ModelState.IsValid)
{
return View();
}
My question. Why is it that ModelState.IsValid is always true in a unit test on the Controller?
Example:
[Test]
public void InValidModelsAreNotAdded()
{
var invalidModel = new MyModel() { SomeField = "some data", SomeOtherField = "" };
var result = _controller.Submit(invalidModel);
_repository.AssertWasNotCalled(r => r.Add(Arg.Is.Anything));
}
Model code:
public class MyModel : IValidatableObject
{
public string SomeField { get; set; }
public string SomeOtherField { get; set; }
public IEnumerable Validate(ValidationContext validationContext)
{
if(string.IsNullOrWhiteSpace(SomeOtherField))
{
yield return
new ValidationResult("Oops invalid.", new[] {"SomeOtherField"});
}
}
}
The AssertWasNotCalled always fails this test.
I stepped through the test and noticed that the ModelState.IsValid is true for this test. It is as if the IValidatableObject.Validate is not being invoked. It seems to work when I run the project, but thats not much of a way to test drive an application.
Also, I realize I could use the [Required] attribute for my example, but my real code has much more complex validation to it.
Thoughts?
It's true because you haven't called anything which sets it false.
This normally happens during binding, but since you just pass the model directly in the test you skip that altogether.
If you're trying to test validation, do that directly. If you're trying to test the error path in your controller, your test's arrange can call _controller.ModelState.AddModelError( //...
Well, insted of simulate the model binding behavior you can do that:
public class YourController : Controller
{
//some code
public ViewResult someAction(Model model)
{
try
{
ValidateModel(model);
}
catch
{
// deal with errors
}
}
//some code
}
ValidateModel with "try catch" blocks are much more readable for me. But you can still use "if" blocks with the method TryValidateModel
Hope that helps!!

Resources