My Web API controller needs to take in a property bag, i.e. a list of key-value pairs, which it will use to insert or update records of indeterminate type. My ideal code would look something like this. This example would be to insert a new record.
Route:
/api/data/{EntityName}
Data from client, passed in using jQuery.ajax() (or similar) with POST:
URI:
http://mysite/api/data/employee
Post data:
"values":
{"FirstName":"Jim",
"LasName":"Nobody",
"StartDate": new Date(),
"Salary": 100000 }
Web API Controller function:
public HttpResponseMessage PutRecord(string entity, ??? values)
{
// confirm valid entity, look up metadata.
// Take values from "values" and feed them into my framework to insert a new employee.
}
What is the ideal configuration to accomplish this? What would be the type for "values" in the controller method - possibly a Dictionary< string,object>?
Thanks very much for your help.
If you create a model that has all of the properties, the model binder will map your post data directly to the model.
So your post would look more like this:
$.post(url, {FirstName:'Jim', LasName: 'NoBody',StartDate: someDate, Salary: 10000},
function(data){
// do something with callback
});
And your action would look like:
public HttpResponseMessage PutRecord(string entity, employeeModel model)
{
// confirm valid entity, look up metadata.
// Take values from "values" and feed them into my framework to insert a new employee.
}
This is a tad more type safe than the property bag approach, and save you a lot of casting and typing issues.
Related
I have written a Web API in ASP.NET Core, for which I need to pass 2 parameters; of them one is a string with grade, the other is of type list of studentInfo as shown here:
[HttpPost]
[Route("UpdateActiveStudents")]
public Response UpdateActiveStudents(string grade, [FromBody] List<StudentsInfo> lststudents)
{
try
{
// My Logic
}
catch(Exception ex)
{
resp.flag = false;
resp.message = ex.Message;
}
return resp;
}
To test this API, I used ARC (Advanced Rest Client). I passed the data as like this in a POST request:
{
"grade": "B",
"lststudents": [
{ "StudentName": "abcdef", "RollNo": "user1"},
{ "StudentName": "abcdef", "RollNo": "user1"}
]
}
It throws a HTTP 400 status error with the following message :
Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[SchoolHub.Model.StudentList]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly. To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object. Path 'lststudents', line 2, position 13.
I'm unaware of this exception.
You have this issue because you are not sending the data in the format at which ASP.Net Web API expects. ASP.net Web API needs some special format when dealing with a value like string and value type (int, bool, etc) parameter are marked with FromBody attribute.
Just remove FromBody it will work. For better understanding go with this link.
Why do we have to specify FromBody and FromUri?
In Web API, general parameter binding rules for POST method are as follows -
Query string -> Primitive type
Request body -> Complex type
Now if you want to use POST method with Mixed parameters i.e in your case you are passing primitive (string) and complex (List), Web API will get the grade parameter from query string and student parameter from the request body.
Possible solutions to try -
In the ARC request it seems you are passing grade in request body instead of as a query string parameter. Try passing grade as a query string parameter.
Also add a class viz. StudentInfoRequest to wrap List<StudentsInfo> lststudents and then use StudentInfoRequest object to pass as parameter to UpdateActiveStudents method.
You dont need to mention [FromBody] in UpdateActiveStudents method as by default complex parameters are read from the request body by Web API.
Hope this helps!
You must add [ApiController] in the controller level. Code is as follows
[ApiController]
[Route("[controller]")]
public class studentController : ControllerBase
{
[HttpPost]
[Route("UpdateActiveStudents")]
public Response UpdateActiveStudents(string grade, List<StudentsInfo>
lststudents)
{
//code
}
}
I have a WebAPI POST controller like the one below:
[ResponseType(typeof(Product))]
public async Task<IHttpActionResult> PostProduct(Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Products.Add(product);
await db.SaveChangesAsync();
return CreatedAtRoute("DefaultApi", new { id = product.Id }, product);
}
To be valid, it expects several values, lets say Name, Price, URL, ManufactureID, StatusID.
However, the POST will not always contain a value for StatusID for example, and therefore the above will fail, as i cannot be null.
But when the value is not sent by the POST, i want to 'intercept' and set the value in code. Let say to int 1.
How would i go about this?
I have been using DTOes for extraxting data from the API, in a nice and viewable way. Can DToes be used in POST also? If so, how? Or any other approach, to setting data, if it does not excist in the POST?
I would say create your Product request model which will be defined in your WebAPI models and there your can define your StatusID as a nullable. After your receive request you can map your Product request data to ProductDto and in that mapping you set your default values if you need them.
Altough you can intercept request on client side and update it but I'm not sure is that something that will work for you.
You should create a POST product class that is agnostic of the persistence. Don't use the generated Product class of your ORM. Using your example above, you should have a ProductModel class that will only contain the properties that the API client can update. Then do the mapping of the DTO to your product data model.
public async Task<IHttpActionResult> PostProduct(ProductModel model)
{
...
var product = db.Products.New();
//mapping here
product.Name = model.Name;
product.Price = model.Price;
}
I have the following controller action:
public JsonResult AcceptChanges(RoomPricing currentData, RoomPricing lastSaveData)
{
...
}
This controller receives JSON encoded objects from my AJAX request:
$.ajax(
{
type: "POST",
url: _controllerURL,
data: JSON.stringify({ currentData: _currentData, lastSaveData: _lastSaveData }),
...
});
The AJAX request data is derived from a form in my partial view, which uses a ViewModel of type RoomPricing. Current and lastSave data are respectively the updated and stale serialized versions of the form. The purpose of these objects is to do form update checks server-side. Server-side because the RoomPricing model includes an enumerable and other junk that would make reliable update checking complex and obnoxious to perform client-side.
Now, when the controller receives the currentData and lastSaveData, it automagically creates the RoomPricing objects, BUT both objects are identical, taking on the values of currentData. To be clear: lastSaveData is created, but only in name, as its contents are identical to that of currentData (and so ignoring the data that was passed by AJAX, which I assume goes into the void).
I expect that this behavior is a side effect of MVC trying to be helpful with Model Binding. Does anyone have a suggestion for getting around this problem? Making a super-ViewModel with two RoomPricing objects to use as the controller argument did not resolve this issue.
Thanks in advance!
Edit: The current and lastSave data comes from the following JQuery code:
var $lastSaveFormData = null;
function AjaxSubmitForm() {
var $lastSaveSerialized = ($lastSaveFormData == null ? null : $lastSaveFormData)
var $form = $('#MyForm');
submitOverrideForm(
$form.serialize(), // currentData
$lastSaveSerialized, // lastSaveData
$form.attr('action'),
);
$lastSaveFormData = $form.serialize();
}
Through the above I'm able to keep a record of all changes since the last save. Although the models are somewhat complex, the size of the data being sent is quite small so I figured I'd compare the data server-side, and here we are. Last thing of note is that I've verified that at this point - JSON.stringify({ currentData: _currentData, lastSaveData: _lastSaveData } - the encoded data is as expected...currentData and lastSaveData are unique if form updates have taken place.
Edit: I just noticed something else. Here is the data being sent to the server as well as the data received server-side just prior to being bound to the model:
_currentData, # AJAX call:
"Book%5B0%5D.Author=1&Book%5B0%5D.Title=100&Book%5B0%5D.IsPaperback=false&Book%5B1%5D.Author=2&Book%5B1%5D.Title=2222&Book%5B1%5D.IsPaperback=false"
_lastSaveData # AJAX call:
"Book%5B0%5D.Author=1&Book%5B0%5D.Title=100&Book%5B0%5D.IsPaperback=false&Book%5B1%5D.Author=2&Book%5B1%5D.Title=77&Book%5B1%5D.IsPaperback=false"
JSON received server-side, just before model binding:
{"currentData": "Book[0].Author: 1,1 Book[0].Title: 100,100
Book[0].IsPaperback: false,false Book[1].Author: 2,2 Book[1].Title:
2222,77 Book[1].IsPaperback: false,false", "lastSaveData":"false"}
Its like MVC is trying to bind the distinct values to a single model, realizes it cant, and drops the 'lastSave' data. Then, when the model hits the controller, it discovers that two models are anticipated, so it uses this model for both. Why is it behaving like this and how can I fix it?
You can add another ViewModel:
public class CurrentAndLastSavedRoomPricingViewModel
{
RoomPricing CurrentData {get;set;}
RoomPricing LastSaveData {get;set;}
}
and get it in controller:
public JsonResult AcceptChanges(CurrentAndLastSavedRoomPricingViewModel data)
{
...
}
passing this model with ajax:
$.ajax(
{
type: "POST",
url: _controllerURL,
data: {data: { currentData: _currentData, lastSaveData: _lastSaveData }},
...
});
Or you can get last saved data on server from database and don't pass it from client.
I've managed to create number of readonly Web Api OData services following the tutorials here: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api. I'm therefore employing the ODataConventionModel builder to create the model from a set of entities (incidentally coming from a Telerik ORM). This all seems to work fine and I can happily issue queries, view the metadata and so forth on the service.
I've now tried to turn my attention to the other CRUD operations - firstly Create and have stumbled into a problem! Namely, the Post method fires correctly (CreateEntity) but the entity parameter is null - by doing a check against the ModelState.IsValid, it shows that the problem is a null ID (key) value. This is unsurprising because the database uses a Database Generated Identity for the ID column and therefore the ID would be created when the entity is saved into the database context.
I've therefore tried all sorts of ways of marking the ID column as database generated, but haven't managed to find anything. Strangely, I can't seem to find even one post of someone asking for this - surely I can't be the only one?!
I noted that when looking at the EF modelbuilder (for example here: http://forums.asp.net/t/1848984.aspx/1) there appears to be a means of affecting the model builder with a .HasDatabaseGeneratedOption property, but no similar option exists in the System.Web.Http.OData equivalent.
So the questions therefore are:
Is there a means of altering the model builder (or something else) so that the controller will accept the object and deserialize the entity even with a null key value?
If so, how can I do this?
If not, any suggestions as to other options?
I realise that I could potentially just populate the object with an (in this case) integer value from the client request, but this seems a) semantically wrong and b) won't necessarilly always be possible as a result of the client toolkit that might be used.
All help gratefully received!
Many thanks,
J.
You need to create a viewmodel for insert which does not contain the ID parameter. Use Automapper to map the properties of the incoming insert-model to your data entities.
The problem that you're having is that ID is a required attribute in your data model because it is your PK, except during insert, where it shouldn't be specified.
In my case, my database-generated key is a Guid.
As a work-around, in my TypeScript client code, I submit (via http POST) the object with an empty Guid like this: Note: ErrorId is the key column.
let elmahEntry: ELMAH_Error = {
Application: 'PTUnconvCost',
Host: this.serviceConfig.url,
Message: message,
User: that.userService.currentUserEmail,
AllXml: `<info><![CDATA[\r\n\r\n${JSON.stringify(info || {})}\r\n\r\n]]></info>`,
Sequence: 1,
Source: source,
StatusCode: 0,
TimeUtc: new Date(Date.now()),
Type: '',
ErrorId: '00000000-0000-0000-0000-000000000000'
};
Then, in my WebApi OData controller, I check to see if the key is the empty guid, and if so, I replace it with a new Guid, like this:
// POST: odata/ELMAH_Error
public IHttpActionResult Post(ELMAH_Error eLMAH_Error)
{
if (eLMAH_Error.ErrorId == Guid.Empty)
{
eLMAH_Error.ErrorId = Guid.NewGuid();
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.ELMAH_Error.Add(eLMAH_Error);
try
{
db.SaveChanges();
}
catch (DbUpdateException)
{
if (ELMAH_ErrorExists(eLMAH_Error.ErrorId))
{
return Conflict();
}
else
{
throw;
}
}
return Created(eLMAH_Error);
}
I have created a simple WCF service that is to be configured by an MVC3 UI.
When I call the index page from my controller, I want to display the values held in the configuration, which has been returned by the service. The user could then chose to edit these settings and then send them back to the service.
I want to do something like this in the index view ...
<div>
#Html.ActionLink("Edit", "Edit", model)
</div>
and then consume the model in the controller like this...
[HttpPost]
public ActionResult Edit( SettingsModel Config)
{
try
{
List<string> configErrors = null;
if (ModelState.IsValid)
{
// Set up a channel factory to use the webHTTPBinding
using (WebChannelFactory<IChangeService> serviceChannel = new WebChannelFactory<IChangeService>(new Uri(baseServiceUrl)))
{
IChangeService channel = serviceChannel.CreateChannel();
configErrors = channel.SetSysConfig(Config);
}
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
but this doesn't work.
Any suggestions???
When the form gets posted, all the input type fields data is collected and sent to the server. You can see this data using FireBug. The key point here is that, is the data that is being posted in a form, that MVC's default model binder can understand and map it to the model object, which is being passed as input parameter to the action method.
In your case, the model is of type "SettingsModel". You have to ensure that, the form data that is being posted is in format, that can be mapped to the "SettingsModel" object.
Same kind of question discussed in another thread : Can't figure out why model is null on postback?
Check Out this article : NerdDinner Step 6: ViewData and ViewModel
In the above article, carefully go through the "Using a ViewModel Pattern" section. My guess is that, this is what you are looking for.
You will need to post the values to populate the SettingsModel object on the Edit action. You can do this using hidden form fields if you don't want the user to see it. Otherwise you could have no parameters on the Edit action and make another call to the web service to populate the Settings model.