Update on any properties with odata - c#

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

Related

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

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

How to retain data gained from multiple requests in EF Core?

I have asp.net core web api with React client. I'm adding data through my user interface created in React. In my api, Db context is added as scoped service, and each time my request finishes and new one is started, all my data from previous request is lost.
This is how my Configure services looks like:
services.AddDbContext<TicketingContext>(o=>o.UseLazyLoadingProxies().UseSqlServer(connectionString));
Controller method for posting data looks like this:
[HttpPost("{id}/tickets")]
public IActionResult CreateNewTicket(string id,
[FromBody] TicketForCreationDto ticketForCreation)
{
if (ticketForCreation == null)
{
return BadRequest();
}
var ticketEntity = _mapper.Map<Ticket>(ticketForCreation);
_ticketRepository.AddNewTicket(ticketEntity);
_ticketRepository.AddTicketToClient(id, ticketEntity);
if (!_ticketRepository.Save())
{
throw new Exception("Creating ticket failed on save");
}
var ticketToReturn = _mapper.Map<TicketDto>(ticketEntity);
return CreatedAtRoute("GetTicket", new {id=id, ticketId = ticketToReturn.Id }, ticketToReturn);
}
and methods in repository like this:
AddNewTicket:
public void AddNewTicket(Ticket ticket)
{
if (ticket.Id == Guid.Empty)
{
ticket.Id = Guid.NewGuid();
}
var dispatcher = AssignTicketToDispatcher(ticket);
if (dispatcher == null)
{
throw new Exception("There are no dispatchers matching this ticket");
}
dispatcher.UserTickets.Add(new UserTicket()
{
IdentityUser = dispatcher,
Ticket = ticket,
UserId = dispatcher.Id,
TicketId = ticket.Id
});
_context.Tickets.Add(ticket);
}
AddTicketToClient:
public void AddTicketToClient(string id, Ticket ticket)
{
var client = _identityUserRepository.GetClient(id);
if (client == null)
{
client = _context.Users.Where(u => u.UserName == "username").FirstOrDefault();
}
client.UserTickets.Add(new UserTicket()
{
IdentityUser = client,
Ticket = ticket,
UserId = client.Id,
TicketId = ticket.Id
});
}
Save:
public bool Save()
{
return (_context.SaveChanges() >= 0);
}
I want to be able to store data gained through multiple requests.
Does anyone have idea how to do that?
Use the database as it's the best method you have for persisting your data.
So When you do a request - at the end of the request, after your latest data is saved - query for the data from previous requests that you need and return it.
e.g. retrieve the last 5 requests saved newest first (where id is example for your primary key field):
var latestSaved = _context.UserTickets.OrderByDescending(x => x.id).Take(5);
Or amend to return all relevant data for e.g. active user by passing a user id stored client side.
Pass through any params you need to request the relevant data.
Use joins / includes set up in your entities. Whatever you need to do - make use of your entity relationships to get what you need from you database. Why try and replicate what it already does? :)

QueryString.Add not adding value to the Request query strings in ASP.NET Core

I am writing a middleware where I want to modify query string values for the current request but I am unable to do that. afaik QueryString.Add method should work but it doesn't affect the query string. Here's what I tried.
public async Task Invoke(HttpContext context, IHeaderValue headerValue, IUser user)
{
var result = context.Request.Headers["Authorization"];
if (result.Count != 0)
{
headerValue.UserId = this.GetUserIdFromToken(result[0]);
var request = context.Request;
if (request.Method == "GET")
{
// It should be adding the new query value to the QueryString collection
// but it doesn't
request.QueryString.Add("CurrentUserId", headerValue.UserId.ToString());
}
}
}
I will really appreciate any help with this.
QueryString.Add returns a new QueryString containing the given name and value. It doesn't mutate the QueryString on which it's called.
So you need to do something like
request.QueryString = request.QueryString.Add("A", "B");

Get file with querystring from formData on c# server side using xmlhttprequest

I have been stuck on this for a couple hours now and not even google can help anymore. I am trying to send a file from the client to the backend using xmlhttprequest. I cannot get the filename, type, or content on the C# side. I would appreciate help on doing this. A lot of code I came across had methods that I can only guess are not supported in ASP.Net 5 and MVC 6 (such as HttpContext.Current and HttpPostedFile)
Here is my client side JavaScript request. This sends the query strings which bind to my model no problem so that is easily accessible, but getting the file is what I am having trouble with.
var form = new FormData();
form.append("file", file);
var queryParams = "id=" + (id == null ? -1 : id);
queryParams += "&name=" + name;
queryParams += "&value=" + val;
xhrAttach(REST_DATA + "/attach?" + queryParams, form, function (item) {
console.log('attached: ', item);
alert(item.responseText);
row.setAttribute('data-id', item.id);
removeProgressIndicator(row);
setRowContent(item, row);
}, function (err) {
console.log(err);
//stop showing loading message
stopLoadingMessage();
document.getElementById('errorDiv').innerHTML = err;
});
function xhrAttach(url, data, callback, errback)
{
var xhr = new createXHR();
xhr.open("POST", url, true);
//xhr.setRequestHeader("Content-type", "multipart/form-data");
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
callback(parseJson(xhr.responseText));
}else{
errback("Error: "+xhr.responseText);
}
}
};
xhr.timeout = 1000000;
xhr.ontimeout = errback;
xhr.send(data);
}
Here is my Controller dealing with the request. attachment is a model and the query string binds to it no problem. I could not find out how to add a File parameter to the model, or if that would even matter. Things I have tried are under this code.
// POST: /api/db/attach
[Route("/api/[controller]/attach")]
[HttpPost]
public async Task<dynamic> attach(Attachment attachment)
{
//get the file somehow
}
i have tried many things, but cannot remember exactly what, here is one thing I did try though, which did not work.
var file = Request.Form["file"];
here is the attachment model in case it helps
namespace MyModel.Models
{
public class Attachment
{
public long id { get; set; }
public string name { get; set; }
public string value { get; set; }
}
}
Don't use query parameters or FormData if you're going to use a model on the MVC side. Just don't. And to me, it's better to just get the file into a base64 string first, than to try sending the File object, itself. I've posted about how to do that, here: Convert input=file to byte array
Then, declare and format a JSON object:
var dataObj = {
file = fileByteArray[0],
id = (id == null ? -1 : id),
name = name,
value = val
};
That fileByteArray[0] is referencing the object from my link. My answer there assumes you were just going to keep loading file base64 strings into that global array object. You can either keep it as an array, like I had, and loop through them one by one, replacing that [0] with [i], for example, as the indexer in a for loop, or just use a var fileByteArray = "" with that other code, make it so you don't push additional files but always just overwrite that variable, & just use that.
And a word of caution on that last parameter - don't use val if you use jQuery - it's a keyword. I only have it above because it's what you were passing to the URL parameter values.
Get rid of the queryParams in this line:
xhrAttach(REST_DATA + "/attach?" + queryParams, form, function (item) {
Change it to:
xhrAttach(REST_DATA + "/attach", form, function (item) {
Set the Content-Type, right where it's commented out, to:
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
Change what you are sending - it's no longer going to be FormData, it's the JSON object, and it needs to be stringified:
xhr.send(JSON.stringify(dataObj));
Fix your model to now include the file base64 string:
public class Attachment
{
public string file { get; set; }
public long id { get; set; }
public string name { get; set; }
public string value { get; set; }
}
Fix your POST method. 2 issues:
You can't use [HttpPost] if your class is inheriting ApiController, which you probably should be for this. It must be [System.Web.Http.HttpPost], and yes, it has to be completely spelled out, or it will assume it's [System.Web.Mvc.HttpPost] and not assign the route - you'd get a 404 - Not Found error when you try to do your POST. If you're inheriting from Controller, disregard this.
You will need a [FromBody] tag on your model if you are inheriting from ApiController:
public async Task<dynamic> attach([FromBody]Attachment attachment) { ... }
Then you get the file like this:
string base64FileString = attachment.file;
If you want to store it in a byte[] in the database, you can convert it:
byte[] bytes = System.Convert.FromBase64String(base64FileString);
And btw, I think your response handling is wrong. I would not do this:
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
callback(parseJson(xhr.responseText));
}else{
errback("Error: "+xhr.responseText);
}
}
};
This is how I would do it:
xhr.onreadystatechange = function(response){
if(xhr.readyState == 4 && xhr.status == 200){
callback(parseJson(response.target.responseText));
} else {
alert("Error: " + response.target.responseText);
}
};
Assuming that the response.target.responseText is getting the error sent back from the server-side in a way you can display. If not, sending it to a function that could parse it out would be the right choice. I don't think that xhr.responseText was correct.
I would suggest trying the following:
public async Task<dynamic> attach([FromURI]Attachment attachment, [FromBody] FormDataCollection formData)
And then the FormDataCollection should have the form data for retrieval.
Add a public get/set property called File to your Attachment model and the uploaded file should be bound to this property.
A model similar to yours:
https://github.com/aspnet/Mvc/blob/9f9dcbe6ec2e34d8a0dfae283cb5e40d8b94fdb7/test/WebSites/ModelBindingWebSite/Models/Book.cs#L8
public class Book
{
public string Name { get; set; }
public IFormFile File { get; set; }
}
Following controller has examples of different ways of model binding an uploaded file.
https://github.com/aspnet/Mvc/blob/9f9dcbe6ec2e34d8a0dfae283cb5e40d8b94fdb7/test/WebSites/ModelBindingWebSite/Controllers/FileUploadController.cs#L81
public KeyValuePair<string, FileDetails> UploadModelWithFile(Book book)
{
var file = book.File;
var reader = new StreamReader(file.OpenReadStream());
var fileContent = reader.ReadToEnd();
var parsedContentDisposition = ContentDispositionHeaderValue.Parse(file.ContentDisposition);
var fileDetails = new FileDetails
{
Filename = parsedContentDisposition.FileName,
Content = fileContent
};
return new KeyValuePair<string, FileDetails>(book.Name, fileDetails);
}
if this does not work, then I suspect your request is not in correct format.

What extra values does Stack Exchange OpenID support?

I am trying to get the username / display name, but I have no idea what are the supported values. I got the email and realname, but I don't know what returns the username / display name.
Is there a documentation or something about this?
My current code:
public class StackExchangeOpenID : OpenIdClient
{
public StackExchangeOpenID()
: base("stackexchange", "https://openid.stackexchange.com")
{
}
protected override Dictionary<string, string> GetExtraData(IAuthenticationResponse response)
{
FetchResponse fetchResponse = response.GetExtension<FetchResponse>();
if (fetchResponse != null)
{
var extraData = new Dictionary<string, string>();
extraData.Add("email", fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email));
extraData.Add("name", fetchResponse.GetAttributeValue(WellKnownAttributes.Name.FullName));
// returned value: null
//extraData.Add("username", fetchResponse.GetAttributeValue(WellKnownAttributes.Name.Alias));
return extraData;
}
return null;
}
protected override void OnBeforeSendingAuthenticationRequest(IAuthenticationRequest request)
{
var fetchRequest = new FetchRequest();
fetchRequest.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
fetchRequest.Attributes.AddRequired(WellKnownAttributes.Name.FullName);
// returned value: null
//fetchRequest.Attributes.AddRequired(WellKnownAttributes.Name.Alias);
request.AddExtension(fetchRequest);
}
}
What's your arrow is pointing to their isn't a display name (StackID has no notion of display names, your login is your email address) but an optional "Vanity Id".
For example:
Gives me the vanity OpenID of https://openid.stackexchange.com/kevin.montrose . This is just an easier to remember alias for relying parties that require manual entry of OpenID urls.
Email and Real Name/Full Name are the only attributes StackID supports querying for, and will return both via either SREG or AX extensions (as seen in the code).

Categories

Resources