Simple serialize ODataQueryOptions - serialization

I'm trying to:
[EnableQuery]
[HttpGet]
[ODataRoute("")]
public IHttpActionResult Get(ODataQueryOptions<UserODataModel> options)
{
var users = _repository.RetrieveOData();
var serialQuery = JsonConvert.SerializeObject(options, jsonOptions);
//save serialQuery somewhere
return Ok(users);
}
But got
Newtonsoft.Json.JsonSerializationException: 'Error getting value from 'ReadTimeout' on 'Microsoft.Owin.Host.SystemWeb.CallStreams.InputStream'.'
"Timeouts are not supported on this stream."
I know there is already a question about serialize Stream:
Newtonsoft Json.net - how to serialize content of a stream?
But in this case i can't "extract stream value" from ODataQueryOptions, or can I?
Some ideia?

Since we work on the same company, if anyone is interested, we found a way, maybe not the pretty way, to serialize an ODataQueryOptions:
public static ODataQueryOptions DeserializeQueryOptions(SerializedQueryOptions options)
{
var uri = new Uri(teste.OriginalUri);
var model = ODataConfig.Model; //GetEdmModel
var segment = model.EntityContainer.FindEntitySet(options.EdmType);
var newPath = new Microsoft.AspNet.OData.Routing.ODataPath(new EntitySetSegment(segment));
var httpConfiguration = new HttpConfiguration();
httpConfiguration.EnableDependencyInjection();
var request = new HttpRequestMessage(HttpMethod.Get, uri)
{
Properties =
{
{ HttpPropertyKeys.HttpConfigurationKey, httpConfiguration },
}
};
var context = new ODataQueryContext(model, options.EntityType, newPath);
var oDataQueryOptions = new ODataQueryOptions(context, request);
return oDataQueryOptions;
}
public static SerializedQueryOptions SerializeQueryOptions(ODataQueryOptions options)
{
return new SerializedQueryOptions
{
OriginalUri = options.Request.RequestUri.AbsoluteUri,
EdmType = options.Context.NavigationSource.Name,
EntityType = options.Context.ElementClrType
};
}
After you serialize it to an object you can serialize it to a JSON string:
var queryOptionsSerialized = new SerializedQueryOptions()
{
OriginalUri = "http://localhost:25723/odata/users?$skip=0&$top=2&$orderby=fullName&$count=true",
EdmType = "users",
EntityType = typeof(UserODataModel)
};
var json = JsonConvert.SerializeObject(queryOptionsSerialized);
var deserialized = JsonConvert.DeserializeObject<SerializedQueryOptions>(json);
var options = ODataQueryOptionsHelper.DeserializeQueryOptions(deserialized);

In case One is not using OData routing or using an ApiController (not ODataController),
modify the way of Obtaining ODataPath to:
ODataUriParser parser = new ODataUriParser(model, serviceRoot, requestUri);
ODataPath path = parser.ParsePath();
//var newPath = new Microsoft.AspNet.OData.Routing.ODataPath(new EntitySetSegment(segment));
Microsoft.AspNet.OData.Routing.ODataPath newPath = new Microsoft.AspNet.OData.Routing.ODataPath(path.FirstOrDefault());
where the serviceRoot is the Url part other that the path defined in the model.

Related

HttpClient not sending post data to NancyFX endpoint

I am doing some integration testing of my web API that uses NancyFX end points. I have the xUnit test create a test server for the integration test
private readonly TestServer _server;
private readonly HttpClient _client;
public EventsModule_Int_Tester()
{
//Server setup
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>());
_server.AllowSynchronousIO = true;//Needs to be overriden in net core 3.1
_client = _server.CreateClient();
}
Inside a Test Method I tried the following
[Fact]
public async Task EventTest()
{
// Arrange
HttpResponseMessage expectedRespone = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
var data = _server.Services.GetService(typeof(GenijalnoContext)) as GenijalnoContext;
//Get come random data from the DBcontext
Random r = new Random();
List<Resident> residents = data.Residents.ToList();
Resident random_residnet = residents[r.Next(residents.Count)];
List<Apartment> apartments = data.Apartments.ToList();
Apartment random_Apartment = apartments[r.Next(apartments.Count)];
EventModel model = new EventModel()
{
ResidentId = random_residnet.Id,
ApartmentNumber = random_Apartment.Id
};
//Doesnt work
IList<KeyValuePair<string, string>> nameValueCollection = new List<KeyValuePair<string, string>> {
{ new KeyValuePair<string, string>("ResidentId", model.ResidentId.ToString()) },
{ new KeyValuePair<string, string>("ApartmentNumber", model.ApartmentNumber.ToString())}
};
var result = await _client.PostAsync("/Events/ResidentEnter", new FormUrlEncodedContent(nameValueCollection));
//Also Doesnt work
string json = JsonConvert.SerializeObject(model, Formatting.Indented);
var httpContent = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _client.PostAsync("/Events/ResidentEnter", httpContent);
//PostAsJsonAsync also doesnt work
// Assert
Assert.Equal(response.StatusCode, expectedRespone.StatusCode);
}
The NancyFX module does trigger the endpoint and receives the request but without the body
What am I doing wrong? Note that the NancyFX endpoint has no issue transforming a Postman call into a valid model.
The NancyFX endpoint
Alright I fixed it, for those curious the issue was that the NancyFX body reader sometimes does not properly start reading the request body. That is that the stream reading position isn't 0 (the start) all the time.
To fix this you need to create a CustomBoostrapper and then override the ApplicationStartup function so you can set up a before request pipeline that sets the body position at 0
Code below
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
base.ApplicationStartup(container, pipelines);
pipelines.BeforeRequest.AddItemToStartOfPipeline(ctx =>
{
ctx.Request.Body.Position = 0;
return null;
});
}

Creating A Service Bus SAS Token and Consuming Relay in WinRT

I have a Service Bus Relay (WCF SOAP) I want to consume in my Windows Store App. I have written the code to create a token as well as the client which is below.
The problem is that I get an AuthorizationFailedFault returned with a faultstring "InvalidSignature: The token has an invalid signature." And I can't figure it out.
My Create Token method:
private static string CreateSasToken()
{
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970,1, 1);
var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + 3600);
string stringToSign = webUtility.UrlEncode(ServiceUri.AbsoluteUri) + "\n" + expiry;
string hashKey = Encoding.UTF8.GetBytes(Secret).ToString();
MacAlgorithmProvider macAlgorithmProvider = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256);
BinaryStringEncoding encoding = BinaryStringEncoding.Utf8;
var messageBuffer = CryptographicBuffer.ConvertStringToBinary(stringToSign,encoding);
IBuffer keyBuffer = CryptographicBuffer.ConvertStringToBinary(hashKey,encoding);
CryptographicKey hmacKey = macAlgorithmProvider.CreateKey(keyBuffer);
IBuffer signedMessage = CryptographicEngine.Sign(hmacKey, messageBuffer);
string signature = CryptographicBuffer.EncodeToBase64String(signedMessage);
var sasToken = String.Format(CultureInfo.InvariantCulture,
"SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
WebUtility.UrlEncode(ServiceUri.AbsoluteUri),
WebUtility.UrlEncode(signature), expiry, Issuer);
return sasToken;
}
My Client class:
public partial class ServiceClient
{
public async Task<string> GetDataUsingDataContract(string item, string sasToken)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("ServiceBusAuthorization",sasToken);
client.DefaultRequestHeaders.Add("SOAPAction",".../GetDataUsingDataContract");
client.DefaultRequestHeaders.Add("Host", "xxxxxxxxxxx.servicebus.windows.net");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,ServiceUri);
var content =new StringContent(#"<s:Envelope
xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Header></s:Header><s:Body>"+ item +#"</s:Body>
</s:Envelope>",System.Text.Encoding.UTF8,"application/xml");
request.Content = content;
HttpResponseMessage wcfResponse = client.SendAsync(request).Result;
HttpContent stream = wcfResponse.Content;
var response = stream.ReadAsStringAsync();
var returnPacket = response.Result;
return returnPacket;
}
}
I have been successful consuming the Relay using Http (via Fiddler) by copying an unexpired token created by Micorosft.ServiceBus in a console app.
I figured out a solution which involved both methods being wrong.
CreateSasToken method:
A minor change involved setting the hashKey variable as byte[] and not string. This line:
string hashKey = Encoding.UTF8.GetBytes(Secret).ToString();
Changed to this:
var hashKey = Encoding.UTF8.GetBytes(Secret);
This change meant that I needed to use a different method to set keyBuffer.
This line:
IBuffer keyBuffer = CryptographicBuffer.ConvertStringToBinary(hashKey,encoding);
Change to this:
IBuffer keyBuffer = CryptographicBuffer.CreateFromByteArray(hashKey);
So the new CreateSasToken method is:
private static string GetSasToken()
{
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + 3600);
string stringToSign = WebUtility.UrlEncode(ServiceUri.AbsoluteUri) + "\n" + expiry;
var hashKey = Encoding.UTF8.GetBytes(Secret);
MacAlgorithmProvider macAlgorithmProvider =
MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256);
const BinaryStringEncoding encoding = BinaryStringEncoding.Utf8;
var messageBuffer = CryptographicBuffer.ConvertStringToBinary(stringToSign,
encoding);
IBuffer keyBuffer = CryptographicBuffer.CreateFromByteArray(hashKey);
CryptographicKey hmacKey = macAlgorithmProvider.CreateKey(keyBuffer);
IBuffer signedMessage = CryptographicEngine.Sign(hmacKey, messageBuffer);
string signature = CryptographicBuffer.EncodeToBase64String(signedMessage);
var sasToken = String.Format(CultureInfo.InvariantCulture,
"SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
WebUtility.UrlEncode(ServiceUri.AbsoluteUri),
WebUtility.UrlEncode(signature),
expiry, Issuer);
return sasToken;
}
Service Client Class
A couple of things to note here.
In order for the request to work, the SAS Token had to be added to the header as a parameter of a AuthenticationValueHeader object. So I added the following method to my helper class (ServiceBusHelper) which held the Key, KeyName and SasToken as properties and the CreateSasToken as a method.
public static AuthenticationHeaderValue CreateBasicHeader()
{
return new AuthenticationHeaderValue("Basic", SasToken);
}
The HttpRequestMessage Content property had to be created a special way. Taking the item parameter passed in, which was a serialized WCF DataContract type I needed to do a few things to make the SOAP envelope. Rather than go through them in detail here is the entire class (one method only). I will comment on the code to handle the response immediately following.
public partial class SalesNotifyServiceClient
{
public async Task<string> GetDataUsingDataContract(string item)
{
string returnPacket = "";
string element = "";
try
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("ServiceBusAuthorization",
ServiceBusHelper.CreateBasicHeader().Parameter);
client.DefaultRequestHeaders.Add("SOAPAction",
".../GetDataUsingDataContract");
client.DefaultRequestHeaders.Add("Host",
"xxxxxxxxxx.servicebus.windows.net");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,
ServiceBusHelper.ServiceUri);
//Creating the request.Content
var encodedItem = item.Replace("<", "<").Replace(">", ">");
var strRequest =
#"<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Header></s:Header><s:Body><GetDataUsingDataContract xmlns=
""http://www.xxxxxxxxxx.com/servicemodel/relay""><item>" +
encodedItem +
#"</item></GetDataUsingDataContract></s:Body></s:Envelope>";
var content = new StringContent(strRequest,
System.Text.Encoding.UTF8, "application/xml");
request.Content = content;
HttpResponseMessage wcfResponse = client.SendAsync(request).Result;
HttpContent stream = wcfResponse.Content;
var response = await stream.ReadAsStringAsync();
//Handling the response
XDocument doc;
using (StringReader s = new StringReader(response))
{
doc = XDocument.Load(s);
}
if (doc.Root != null)
{
element = doc.Root.Value;
}
returnPacket = element;
}
catch (Exception e)
{
var message = e.Message;
}
return returnPacket;
}
}
In order to get at the DataContract object I had to do a few things to the response string. As you can see at the //Handling the response comment above, using StringReader I loaded the returned SOAP envelope as a string into an XDocument and the root value was my serialized DataContract object. I then deserialized the returnPacket variable returned from the method had my response object.

How can I provide Metadata from my WCF service for consumption in Breeze

I am trying to adapt an existing WebApi/MVC4 app to use Breeze lookups.
Currently I retrieve my DTOs via
[HttpGet]
public IQueryable<ThingDto> GetThings()
{
var channelFactory = ThingServiceConfiguration.CreateChannelFactory();
_serviceFactory = () => new WcfProxy<IThingService>(channelFactory.CreateChannel());
var client = _serviceFactory();
IQueryable<ThingDto> result = client.Execute(p => p.GetThings()).OrderBy(x => x.Name).AsQueryable();
return result;
}
I'm not sure how I implement this method Metadata()
public string Metadata()
{
//normally something like this if using a EF DataContext
// return _someContextProvider.Context.Things;
}
How I setup the WCF config
public class ThingServiceConfiguration
{
const string AppSettingKey = "ThingServiceUrl";
public static ChannelFactory<IThingService> CreateChannelFactory()
{
// var serviceUrl = ConfigurationManager.AppSettings[AppSettingKey];
var serviceUrl = "http://localhost:86/ThingService.svc";
var binding = new BasicHttpBinding(BasicHttpSecurityMode.None)
{
MaxReceivedMessageSize = 200000000,
SendTimeout = TimeSpan.FromMinutes(2),
ReceiveTimeout = TimeSpan.FromMinutes(2)
};
var address = new EndpointAddress(serviceUrl);
return new ChannelFactory<IThingService>(binding, address);
}
}
Could this metadata be provided with the WCF call into the Metadata() property (by providing arguments through BasicHttpBinding ?
Many thanks!
You can return Breeze 'native' metadata simply by returning the metadata in json form. Something like this:
[HttpGet]
public String Metadata() {
var folder = Path.Combine(HttpRuntime.AppDomainAppPath, "App_Data");
// metadata.json is the name of a file containing your metadata - pick any file name you like.
var fileName = Path.Combine(folder, "metadata.json");
var jsonMetadata = File.ReadAllText(fileName);
return jsonMetadata;
}
where the syntax of the metadata file is described here: Breeze metadata format.

JSON.Net serialize XML to JSON camel case

How do I get JSON.Net to serialize my XML to camel case JSON and without the "#"?
this is what I currently have but it prepends # to the properties and does not camel case...
XmlDocument doc = new XmlDocument();
doc.LoadXml(myXmlString);
string jsonText = Newtonsoft.Json.JsonConvert.SerializeObject(doc, new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
Make a model of your XML data
example
public class MyClass
{
[JsonProperty("#SomeXMLProperty")]
public string MyString{ get; set; }
}
then deserialize XML to your model
XDocument xmlDocument = XDocument.Parse(xmlData);
string jsonData = JsonConvert.SerializeXNode(xmlDocument);
var myClass = JsonConvert.DeserializeObject<MyClass>(jsonData);
then just use CamelCasePropertyNamesContractResolver and Formatting.Indented
string json = JsonConvert.SerializeObject(rootObject,
Newtonsoft.Json.Formatting.Indented,
new JsonSerializerSettings { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver() });
UPDATE:
The first solution is simple and clean (no need to write custom resolvers etc.) this is just for removing # sign
var xml = new XmlDocument();
xml.XmlResolver = null;
xml.Load("yourfilehere");
var json = JsonConvert.SerializeXmlNode(xml, Newtonsoft.Json.Formatting.Indented);
var withoutATSign = System.Text.RegularExpressions.Regex.Replace(json, "(?<=\")(#)(?!.*\":\\s )", String.Empty, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
If anybody knows a better solution for both cases then the first it would be nice to look at it.
WebAPI addition
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

Can I implement file download using MVC4's ApiController?

Using regular Controller I could do it by returning FileResult. The same doesn't seem to work with ApiController. Can it be done? Is it even a right thing to do?
Try this.
[HttpGet]
public HttpResponseMessage Get()
{
var file = HttpContext.Current.Server.MapPath("~/Images/accent.png");
var stream = new FileStream(file, FileMode.Open);
var content = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(stream)
};
content.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return content;
}
I have this working thanks to this question.
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web.Http;
namespace Web.Controllers
{
//usage: /download/report
[RoutePrefix("download")]
public class DownloadController : ApiController
{
[HttpGet("report")]
public HttpResponseMessage Report()
{
using (var service = new Client())
{
var report = service.BuildReport();
return DownloadResponse(report, "Report.csv");
}
}
private static HttpResponseMessage DownloadResponse(string content, string fileName)
{
var downloadContent = new StringContent(content);
var mediaType = new MediaTypeHeaderValue("application/octet-stream");
var disposition= new ContentDispositionHeaderValue("attachment") { FileName = fileName };
downloadContent.Headers.ContentType = mediaType;
downloadContent.Headers.ContentDisposition = disposition;
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = downloadContent
};
return result;
}
}
}