How can I pass an object (class) to Rest Service - c#

In my REST Service I have the following:
AssetController:
// GET: <AssetController>
[HttpGet("{companyID}/{machineName}")]
public Asset Get(int companyID, string machineName)
{
Database db = new Database(configuration.ConnectionString);
//DataSet ds = db.executeFunctionSelect("fngetallassets2()");
DataSet ds = db.executeViewSelect("tblasset where LOWER(name) = '" + machineName.ToLower() + "'");
//DataSet ds = db.executeDataSetProc("getallassets", null);
DataTable table = ds.Tables[0];
DataRow row = table.Rows[0];
Asset asset = new Asset
{
ID = int.Parse(row["ID"].ToString()),
CompanyID = int.Parse(row["Company_ID"].ToString()),
Name = row["Name"].ToString(),
IPAddress = row["IP_Address"].ToString(),
CreateDate = DateTime.Parse(row["Create_Date"].ToString()),
IsActive = bool.Parse(row["Is_Active"].ToString())
};
return asset;
}
This works fine... Its the PUT that I need help with
// PUT /<AssetController>/5
// Insert record into the database
[HttpPut("{asset}")]
public void Put([FromBody] string asset)
{
Database db = new Database(configuration.ConnectionString);
db.executeNonQuery("sp_AssetInsert", null);
}
Here I am trying to pass (somehow) the same asset class
In the calling windows forms I use this way to call the PUT Method:
public void InsertAsset(Asset asset)
{
ArrayList parameters = new ArrayList
{
asset.Name,
asset.IPAddress
};
RestClient client = new RestClient("https://localhost:5001/Asset/");
RestRequest request = new RestRequest(Method.PUT);
request.AddJsonBody(asset);
IRestResponse<List<string>> response = client.Execute<List<string>>(request);
if (response.StatusCode == HttpStatusCode.OK)
{
}
I get an error on Response.StatusCode = unsupportedmedia or something like this.
I need to know how to serialize or somehow pass either the class or the JSON string of it or whatever...
Can someone please help me figure out how to call the PUT methods as I have dozens of these to do.

Here is the calling and receiving code used to make this work.
calling:
RestClient client = new RestClient("https://localhost:5001/Asset/");
RestRequest request = new RestRequest(Method.PUT);
request.AddJsonBody(asset); <-- Asset is a class object
RestResponse response = (RestResponse)client.Execute(request);
if (response.StatusCode == HttpStatusCode.OK)
{
}
Receiving Code:
// PUT /<AssetController>/5
// Insert record into the database
[HttpPut]
public void Put([FromBody] Asset asset)
{
Database db = new Database(configuration.ConnectionString);
db.executeNonQuery("sp_AssetInsert", null);
}
I needed to change the [FromBody] string asset to [FromBody] Asset asset

There are several ways to pass parameters:
as url route i.e. https://localhost:5001/Asset/42/MyCompanyName
as url parameter http:// localhost:5001/Asset?companyID=42&machineName=companyname
in body, typically as a json serialized object
when you specify the route in [HttpPut("{paramaters}")] you are specifying option 1. You can use FromBody and FromUrl attributes on the parameter to control this. Simple parameters like numbers and string would typically be part of the URL, while complex objects like Asset will probably be easier to pass in the body.
See also
restsharp parameter posting
asp.net parameter binding

Related

IdentityServer4.Models.Client as httppost parameter always null in aspnet core 2.x

I am sending an httpPost parameter "client" of type IdentityServer4.Models.Client via a C# console application to a C# web api and client is always null.
If I send a different object using the same code, the parameter is received just fine. This seems to be a problem specific to Identity Server 4 and I don't know what's going on.
Server code:
[HttpPost]
public JsonResult Post(IdentityServer4.Models.Client client){
return Json(new { result = true });
}
Client code:
private static async Task Register(string clientName){
var controllerName = "BasicClient";
var basicClientApi = string.Format("http://localhost:5100/api/{0}", controllerName);
using (var httpClient = new HttpClient()){
var clientData = new IdentityServer4.Models.Client();
var client = new { client = clientData };
client.client.ClientName = clientName;
var json = JsonConvert.SerializeObject(client);
var content = new StringContent(json, Encoding.UTF8, "application/json");
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await httpClient.PostAsync(basicClientApi, content);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var rawResponse = await response.Content.ReadAsStringAsync();
JObject o = JObject.Parse(rawResponse);
Console.WriteLine(o.ToString());
}
}
}
EDIT
After applying [FromBody] and unwrapping the object, I am still getting null for client in the receiving Web API. One thing caught my eye on the console application debug screen though.. see the image below:
The actual client variable is null in the console application, yet JsonConvert was able to serialize it into something. What's that about?
You are wrapping your model inside an anonymous object, which will turn its JSON representation into something which has nothing in common with your original Client class.
This:
var clientData = new IdentityServer4.Models.Client();
var client = new { client = clientData };
client.client.ClientName = clientName;
var json = JsonConvert.SerializeObject(client);
Will result in a JSON similar to the following:
{
"client": {
"clientName": "foo",
"anotherProperty": "bar",
// other properties omitted for brevity
}
}
But what you really want is just the Client object:
{
"clientName": "foo",
"anotherProperty": "bar",
// other properties omitted for brevity
}
Do not wrap your clientData, just serialize it directly to follow the model inside your MVC Action Method:
var clientData = new IdentityServer4.Models.Client();
clientData.ClientName = clientName;
var json = JsonConvert.SerializeObject(clientData);
For everything to be working, you have to tell the model binder explicitly where to expect the data.
Use [FromBody] attribute on the model.
[FromBody]: Use the configured formatters to bind data from the request body. The formatter is selected based on content type of the request.
[HttpPost]
public IActionResult Post([FromBody]IdentityServer4.Models.Client client) {
return Json(new { result = true });
}
Reference Model Binding in ASP.NET Core
You're not going to believe this, but ultimately the reason why IdentityServer.Models.Client was null in the Web Api post parameter was because the class is decorated with [DebuggerDisplay("{ClientId}")] and I did not provide a ClientId in my test application, so it was always showing up as null when in fact it actually WAS THERE THE WHOLE TIME. I am glad this issue is behind me, but I am very angry about this "feature".

How to send files along with the viewModel using Web API or how to save using temporary data

I've read many stackoverflow posts with the similar problems as well as several blogs but I am still uncertain as how to solve my problem :(
I have angularJS directive that allows to upload files to the server. The code is like this:
[HttpPost]
[Route("UploadFile")]
public async Task<HttpResponseMessage> UploadFile()
{
// Check if the request contains multipart/form-data.
if (Request.Content.IsMimeMultipartContent("form-data"))
{
try
{
var resultOut = new List<FileUploadResult>();
var streamProvider = new MultipartMemoryStreamProvider();
streamProvider = await Request.Content.ReadAsMultipartAsync(streamProvider);
foreach (
var item in
streamProvider.Contents.Where(c => !string.IsNullOrEmpty(c.Headers.ContentDisposition.FileName))
)
{
FileUploadResult file = new FileUploadResult()
{
FileName = item.Headers.ContentDisposition.FileName,
// Content = fileBytes, // No need to pass the info back as we're not going to read it save it yet
Key = Guid.NewGuid().ToString(),
Type = item.Headers.ContentDisposition.DispositionType
};
resultOut.Add(file);
//using (Stream stFileSource = new MemoryStream(await item.ReadAsByteArrayAsync())) {
// byte[] fileBytes;
// fileBytes = new Byte[stFileSource.Length];
// stFileSource.Read(fileBytes, 0, Convert.ToInt32(stFileSource.Length));
// FileUploadResult file = new FileUploadResult()
// {
// FileName = item.Headers.ContentDisposition.FileName,
// // Content = fileBytes, // No need to pass the info back as we're not going to read it save it yet
// Key = Guid.NewGuid().ToString(),
// Type = item.Headers.ContentDisposition.DispositionType
// };
// resultOut.Add(file);
//}
}
return Request.CreateResponse(HttpStatusCode.OK, resultOut.ToArray());
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
Also directive saves the Files array into a property. My user form allows to remove some files / add more files and then I want to save the information from the form (somewhat complex view model) along with the files. I was unable to figure that problem so far. One possibility I see here is to save the files in the UploadFile method using Repository into a database. However, I would prefer to save that into some temporary table instead (e.g. #FileInfo table) and not the actual table. Or perhaps there is a way to save files (with its binary content) into some memory object so I will be able to get that content back when I am ready to save my model's data? Can you either show implementation of the temporary repository storage or give some other ideas for my dilemma?
Firstly, Your directive need to create a post request with 'multipart/form-data'.
Check this link for reference.
However, we use angular file upload to do this.
angular
.module('app', ['angularFileUpload'])
.controller('AppController', function($scope, FileUploader) {
$scope.uploader = new FileUploader(
{
url: 'Your/upload/url',
headers: {
'autorization': 'Bearer token if you need it'
},
onProgressItem: function () {
...
},
onSuccessItem: function (opt, data) {
...
},
onErrorItem: function (opt) {
...
}
});
//you may want to wrap the following in an event
var uploadItem = $scope.uploader.queue[uploader.queue.length - 1];
uploadItem.formData.push({
someData: "someData",
moreData: "moreData"
});
uploadItem.upload();
uploadItem.formData = [];
});
Then in your controller, you can do the following to retrieve what you need:
//your request
var request = HttpContext.Current.Request;
//your fields
var someData = request.Form["someData"];
var moreData = request.Form["moreData"];
//your file
var file = request.Files["file"];
Looks like a job for TempData:
TempData in ASP.NET MVC is basically a dictionary object derived from
TempDataDictionary. TempData stays for a subsequent HTTP Request as
opposed to other options (ViewBag and ViewData) those stay only for
current request. So, TempdData can be used to maintain data between
controller actions as well as redirects.
example:
//Controller Action 1 (TemporaryEmployee)
public ActionResult TemporaryEmployee()
{
Employee employee = new Employee
{
EmpID = "121",
EmpFirstName = "Imran",
EmpLastName = "Ghani"
};
TempData["Employee"] = employee;
return RedirectToAction("PermanentEmployee");
}
//Controller Action 2(PermanentEmployee)
public ActionResult PermanentEmployee()
{
Employee employee = TempData["Employee"] as Employee;
return View(employee);
}

Convert the ExecuteReader in to XML HTTP Response

I have an Web API craeted which calls the stored procedure, the stored procedure insert/updates and select a record in the Database. Insert/Update works, trying to convert the selected record that is read using ExecuteReader in to HTTPResponseMessage
[HttpGet]
public HttpResponseMessage Get(string Account)
{
if (string.IsNullOrEmpty(Account))
{
return Request.CreateResponse(new { error = "Input parameters cannot be Empty or NULL" });
}
string strcon = ConfigurationManager.ConnectionStrings["DBConnection"].ConnectionString;
SqlConnection DbConnection = new SqlConnection(strcon);
SqlDataReader reader = null;
DbConnection.Open();
SqlCommand command = new SqlCommand("[dbo].[usp_InserUpadte]", DbConnection);
command.CommandType = CommandType.StoredProcedure;
//create type table
DataTable table = new DataTable();
table.Columns.Add("AccountID", typeof(string));
table.Rows.Add(Account);
SqlParameter parameter = command.Parameters.AddWithValue("#account_TT", table);
parameter.SqlDbType = SqlDbType.Structured;
parameter.TypeName = "account_TT";
XmlReader xreader = command.ExecuteXmlReader();
List<QueryResult>qresults = new List<QueryResult>();
while (xreader.Read())
{
QueryResult qr = new QueryResult();
qr.AccountID = xreader["AccountID"].ToString();
qr.CounterSeq = xreader["CounterSeq"].ToString();
qresults.Add(qr);
}
I am not sure how to build the Response in XML, I created a class called QueryResult but I am not sure if this can be used in create the XML Response.
public class QueryResult
{
public string AccountID { get; set; }
public string CounterSeq { get; set; }
}
Also I am trying to write theresponse in the file when the API is executed. I havedone this with JSON and OracleDatabase earlier and not sure with this one. Any help is greatly appreciated.
You just need to return your qresults object and it will be automatically serialized into XML (or JSON). You can read how to add attributes to your QueryResult class to get the xml structured the way you would like it here JSON and XML Serialization in Web API
I would also recommend returning a IHttpActionResult instead of building a HttpResponseMessage up. Then you can simply do this at the end of your message and it will automatically be serialized based on the http ACCEPT header:
[HttpGet]
public IHttpActionResult Get(string Account)
{
// rest of implementation left out for readability...
return Ok(qresults);
}

Pass class name to method

I am writing a Windows Phone 8.1 App (WINRT). I have to call HTTPHandlerMethod method accepting three parameters. I pass Server API address as string, JsonString to be send to server as string, and also I need to send class name to HTTPHandlerMethod also as third parameter. How to send class name? I actually need to use class name inside this method in JSON DeSerializing:
JsonConvert.DeserializeObject(JSonData_Recieved,CLASS NAME HERE);
public async void HTTPHandlerMethod(string AddressPath,
string JSonData_ToSend, **WHAT THIRD PARAMETER TO WRITE HERE??**)
{
Object resObject = null;
HttpBaseProtocolFilter HttpBaseProtocolFilterObject = new HttpBaseProtocolFilter();
HttpClient HttpClientObject = new HttpClient(HttpBaseProtocolFilterObject);
string CompleteAddress = singletonInstance.APIServer + AddressPath;
Uri UriObject = new Uri(CompleteAddress);
HttpRequestMessage HttpRequestMessageObject =
new HttpRequestMessage(HttpMethod.Post, UriObject);
HttpRequestMessageObject.Content = new HttpStringContent(JSonData_ToSend,
Windows.Storage.Streams.UnicodeEncoding.Utf8, "application/json");
try
{
HttpResponseMessage HttpResponseMessageObject =
await HttpClientObject.SendRequestAsync(HttpRequestMessageObject,
HttpCompletionOption.ResponseContentRead);
if (HttpResponseMessageObject.IsSuccessStatusCode) //If 2xx success is recieved
{
string JSonData_Recieved =
await HttpResponseMessageObject.Content.ReadAsStringAsync();
resObject = JsonConvert.DeserializeObject(JSonData_Recieved,resType);
}
}
catch { }
}
}
What modifications should I do to this HTTPHandlerMethod method? and how to call it then?
public async void HTTPHandlerMethod(string AddressPath,
string JSonData_ToSend, Type classname)
{
}

Update on any properties with odata

Do you know how I could update an entity in WCF Data Services with OData syntax without the key property of the entity.
For example, an entity:
public class Product
{
[Key]
public int Id { get; set; }
public string Reference { get; set; }
}
I would like to make this request:
PUT myservice.svc/Product('REFXX')
with 'REFXXX' corresponding do the Reference property (which is unique).
Any idea?
Currently there is no way to do this - the issue is if you pass the following request to the server (PUT myservice.svc/Product('REFXX')), how will the server know that REFXX is the value for the unique property and not the key property.
If you really want to update the client based on the unique property, make sure the server exposes that unique property as key.
Thanks
Pratik
I wrote a IDispatchMessageInspector, parse the url and replace the match element in the request parameter with a correct syntax and the real key. I know that the key is not the real "Key" with a specific user agent or with the syntax Service.svc/Entity(SecondaryKey=value), which is used normally for multiple pk's.
so in the method AfterReceiveRequest the process is:
parse the url Service.svc/Entity(SecondaryKey=value)
get the key value of the entity (by building a dynamic linq expression)
change the match element of the request with Service.svc/Entity(PKValue)
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
if (request.Properties.ContainsKey("UriTemplateMatchResults") && HttpContext.Current != null)
{
//get match for current request
UriTemplateMatch match = (UriTemplateMatch)request.Properties["UriTemplateMatchResults"];
Utils.ODataBasicUriParser uriParser = new Utils.ODataBasicUriParser(match.RequestUri.PathAndQuery);
//verify if this is a SecondaryKey request
if (uriParser.IsEntityQuery && uriParser.IsSecondaryKeyQuery)
{
//TODO this syntax is also used for entities with multiple pk's, test it
//get a new data context
//TODO see if this can be improved, avoid two datacontext for one request
DataContext ctx = new DataContext();
Type outType;
//get entity type name from the service name
string entityName = DataContext.GetEntityNameByServiceName(uriParser.EntityServiceName);
//get the pk for the entity
string id = ctx.GetEntityId(entityName, uriParser.EntityKey, uriParser.EntityId, out outType);
//verify if the pk has been found or cancel this to continue with standart request process
if (string.IsNullOrEmpty(id))
{
Trace.TraceWarning(string.Format("Key property not found for the the entity:{0}, with secondaryKeyName:{1} and secondaryKeyValue:{2}",
entityName, uriParser.EntityKey, uriParser.EntityId));
return System.Net.HttpStatusCode.NotFound;
}
//in odata syntax quotes are required for string values, nothing for numbers
string quote = outType.FullName == typeof(Int32).FullName || outType.FullName == typeof(Int64).FullName ? string.Empty : "'";
//build the new standart resource uri with the primary key
var newUri = new Uri(string.Format("{0}/{1}({2}{3}{2})", match.BaseUri.ToString(), uriParser.EntityServiceName, quote, id));
//create a new match to replace in the current request, with the new Uri
UriTemplateMatch newMatch = NewMatch(match, newUri);
//set request values
request.Properties["UriTemplateMatchResults"] = newMatch;
request.Headers.To = newUri;
request.Properties.Via = newUri;
}
}
return null;
}
UriTemplateMatch NewMatch(UriTemplateMatch match, Uri newUri)
{
UriTemplateMatch newMatch = new UriTemplateMatch();
newMatch.RequestUri = newUri;
newMatch.Data = match.Data;
newMatch.BaseUri = match.BaseUri;
return newMatch;
}
works for my current needs

Categories

Resources