WebApi & Odata - suitable for file management? - c#

I am moving most / all of my API in a project over to Odata from "pure" WCF and using an OWIN hosted Odata enpoint for this.
The one element I am stuck with at the moment are files. I have 2 areas where I need to upload a ZIP file to the server for processing. In one case that is attached to an Entity (called a "Repository") and contains binary content that is not exposed via Odata (it is just uploaded). In the other hand this is for an unbound action and the ZIP file contains configuration files which will crate/change a number of entities.
Is that feasible with OData, or should I ignore Odata for this and go with "Manually configured" standard endpoints? I really would love to keep this in Odata due to the exposed metadata.
Before anyone comments - I have been trying to find documentation via google, but I keep getting no relevant answers. The answers I do get indicate this is possible, but all have code examples that point to the old WCF level API, while I use WebApi. The documentation at http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/create-an-odata-v4-endpoint does not go into too many details. It does not show the allowed types for a Parameter configuration for an action and how to configure it to accept the file via http post from a web form (and a client because I will need both).

Here is a useful link with documentation about Media Resource Support for OData in Web API: https://blogs.msdn.microsoft.com/mrtechnocal/2013/10/31/media-resource-support-for-odata-in-web-api/
You can simplify a little bit the implementation proposed in the link, but for sure, you will need to:
Create an OData Media Resource controller. It can be simpler than the one proposed in the document. See below.
Create a custom EntityRoutingConvention to route properly to the Get methods that will return the ZIP files (in case you have that use case, if you only need to POST them, you may not need the custom routing convention).
So, for the controller, you can have:
public abstract class YourMediaResourceODataController<TEntity, TKey>
: ODataController where TEntity : class
{
}
And then the real controller:
public class YourController : YourMediaResourceODataController<YourZIPObjectEntity, string>
{
// This would be the post
public async Task<IHttpActionResult> Post()
{
var stream = await Request.Content.ReadAsStreamAsync();
// Manage the stream
}
// The get (if you want it, you will need to code the custom EntityRoutingConvention).
[HttpGet]
public HttpResponseMessage GetMediaResource(string key)
{
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var theZIPFile = yourZIPFileService.GetZIPFileByKey(key);
StreamContent contentResult;
using(var ms = new MemoryStream(theZIPFile.theByteArray)
{
contentResult = new StreamContent(ms);
}
result.Content = contentResult;
return result;
}
}
You will need to have an entity YourZIPObjectEntity with a Stream/byte[]/string property, depending on how you manage the binary file. (In the documentation example this is the Image class). And for that entity you will need to specify that it has a stream in the ODataConfig (see the "Setting Up the Web API Configuration" section in the documentation).
I think that is pretty much all.
Then, you can, from code, POST your ZIP files as a StreamContent:
using(var requestContent = new MemoryStream(yourByteArray))
using(var request = new HttpRequestMessage(HttpMethod.POST, yourPOSTUri)
{
request.Content = new StreamContent(requestContent);
// Set headers and send the request...
}
I hope this is the solution you are looking for, or at least an approach to it.

For files with simple binary content you could use WebApi rather than OData. Unless there's a repository of files that you want to serve to the consumer.
If you must upload zip files and processing them manually in order to modify entities you don't need to use OData either. However WebApi OData does provide batch transactions support. You could follow this tutorial: https://blogs.msdn.microsoft.com/webdev/2013/11/01/introducing-batch-support-in-web-api-and-web-api-odata/
Again if you have large batches rather than sending zip files use gZip compression. Here's a neat post about WebApi gZip support: https://damienbod.com/2014/07/16/web-api-using-gzip-compression/

Related

WordPress WooCommerce ASP.net API WebHookHandler: The WebHook request must contain an entity body formatted as HTML Form Data

I am trying to create a WebHookHandler for Webhooks send from WordPress WooCommerce in ASP.NET C#.
I started with creating a ASP.NET C# Azure API App WebApplication Project and adding the relevant references (Microsoft.AspNet.WebHooks.Common, Microsoft.AspNet.WebHooks.Receivers, Microsoft.AspNet.WebHooks.Receivers.WordPress). Added the WebHookConfig, WordPressWebHookHandler and registered the WebHookConfig in the GlobalAsax.
I then published the application as an Azure App Service.
My WordPressWebHookHandler is still the default of the examples and looks like this:
public class WordPressWebHookHandler : WebHookHandler
{
public override Task ExecuteAsync(string receiver, WebHookHandlerContext context)
{
// make sure we're only processing the intended type of hook
if("WordPress".Equals(receiver, System.StringComparison.CurrentCultureIgnoreCase))
{
// todo: replace this placeholder functionality with your own code
string action = context.Actions.First();
JObject incoming = context.GetDataOrDefault<JObject>();
}
return Task.FromResult(true);
}
}
When testing a User Creation WebHook in WooCommerce I can see the request in the log as below.
But unfortunately it is never received while debugging and I see below error.
I am thinking maybe I need a custom WebHook instead of the WordPress specific one as this is a WooCommerce Webhook. Or possibly it is handled wrong in the routing and ends up in another controller.
Any help is much appreciated.
Your WebHookReceiver is wrong
There is a mismatch of expecting HTML Form Data, when in fact it should be expecting JSON.
WordPressWebHookHandler is still the default
This is what is causing your error. If you look at the WordPressWebHookReceiver, the ReceiveAsync() method implementation, calls out to ReadAsFormDataAsync() method, which is not what you want, as your Content-Type is json. So, you want to be doing ReadAsJsonAsync().
Solution: Don't use the WordPressWebHookReceiver and switch it to another one that will call ReadAsJsonAsync().
Looking at the code
I am thinking maybe I need a custom WebHook instead of the WordPress specific one as this is a WooCommerce Webhook.
You had the right idea, so I dug up some of the code to explain exactly why this was happening.
The code block below is the ReceiveAsync() method that is overridden in the WordPressWebHookReceiver. You can see that it is calling the ReadAsFormDataAsync() which is not what you want...
public override async Task<HttpResponseMessage> ReceiveAsync(
string id, HttpRequestContext context, HttpRequestMessage request)
{
...
if (request.Method == HttpMethod.Post)
{
// here is what you don't want to be called
// you want ReadAsJsonAsync(), In short, USE A DIFFERENT RECEIVER.
NameValueCollection data = await ReadAsFormDataAsync(request);
...
}
else
{
return CreateBadMethodResponse(request);
}
}
A quick search through the repository for classes that call the ReadAsJsonAsync() method, shows that the following recievers implement it:
DynamicsCrmWebHookReceiver
ZendeskWebHookReceiver
AzureAlertWebHookReceiver
KuduWebHookReceiver
MyGetWebHookReceiver
VstsWebHookReceiver
BitbucketWebHookReceiver
CustomWebHookReceiver
DropboxWebHookReceiver
GitHubWebHookReceiver
PaypalWebHookReceiver
StripeWebHookReceiver
PusherWebHookReceiver
I assumed that the CustomWebHookReceiver would fit your requirements, so can grab the NuGet here. Otherwise you can implement your own, or derive it from this class, etc.
Configuring a WebHook Recevier
(Copied from the Microsoft Documentation)
Microsoft.AspNet.WebHooks.Receivers.Custom provides support for
receiving WebHooks generated by ASP.NET WebHooks
Out of the box you can find support for Dropbox, GitHub, MailChimp,
PayPal, Pusher, Salesforce, Slack, Stripe, Trello, and WordPress but
it is possible to support any number of other providers
Initializing a WebHook Receiver
WebHook Receivers are initialized by registering them, typically in
the WebApiConfig static class, for example:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
...
// Load receivers
config.InitializeReceiveGitHubWebHooks();
}
}
There is a problem with the data format that you send in your request. You must use format of HTML Form as your error message said.
Proper POST data format is described here: How are parameters sent in an HTTP POST request?
Don't forget to set Content-Length header and correct Content-Type if your library doesn't do it. Usually the content type is application/x-www-form-urlencoded.
I would like to make some additions to Svek's answer as I now got my Proof-of-concept completed and understand a bit more about the receivers.
His answer pointed me in the right direction, but needs a little addition.
WordpressWebHookReceiver
Can take in Wordpress Webhooks of type HttpPost. This does not work with Woocommerce as Woocommerce sends Json Webhook messages and will fail the HttpPost validation which is build into the WordpressWebHookReceiver class.
CustomWebHookReceiver
Can take in custom ASP.NET Webhooks. The custom ASP.NET webhooks have a specific partner for validation which includes but is not limited to the 'ms-signature'. Even adding the header will not suffice as the signature is also used in a different way from out of the box Woocommerce to encrypt the message. Basically coming to a point that you can't integrate Woocommerce with the CustomWebHookReceiver without changing the Webhook classes of Woocommerce.
GenericWebHookReceiver
This is the receiver you want, which accepts basically a generic set of Json data and will be able to use the "code" query parameter to verify the secret which you can add in the web.config of your asp.net api application. I used this receiver to finish the Proof-of-concept and got both the signature validation as well as the deciphering of the message working right of the bat.
My basic class which I will start to build into a real solution can be viewed below and changes the JObject into a dynamic object in the methods I call from the class. As you can see I have two methods currently added, one for the customer create and one for the order create to call the respective methods which do an insert into Dynamics 365 (former CRM).
public class GenericJsonWebHookHandler : WebHookHandler
{
public GenericJsonWebHookHandler()
{
this.Receiver = "genericjson";
}
public override Task ExecuteAsync(string generator, WebHookHandlerContext context)
{
var result = false;
try
{
// Get JSON from WebHook
var data = context.GetDataOrDefault<JObject>();
if(context.Id != "crcu" && context.Id != "cror")
return Task.FromResult(true);
if (context.Id == "crcu")
{
result = WoocommerceCRMIntegrations.Entities.Contact.CreateContactInCRM(data);
}
else if (context.Id == "cror")
{
result = WoocommerceCRMIntegrations.Entities.Order.CreateOrderInCRM(data);
}
}
catch (Exception ex)
{
result = false;
}
return Task.FromResult(result);
}
}

Generic and efficient way to send files from a Controller to remote ApiController in ASP.NET MVC

I am working on a multi-level ASP.NET MVC web platform and I have the requirement to send a file from a System.Web.Mvc.Controller to an ASP.NET MVC web-service's System.Web.Http.ApiController, running remotely on a different machine.
Currently, I have this in my Mvc.Controller:
public ActionResult ForwardThisFile(HttpPostedFileBase file)
{
// TODO: Forward file to remote DocumentApi:
DocumentApi.DocumentApiClient client = new DocumentApi.DocumentApiClient();
client.StoreDocument(file /* <-- How-To? */);
return View();
}
where the DocumentApiClient has been generated via Visual Studio's Add REST API Client... function from a Swagger Url. (The generated client internally uses the Microsoft.Rest.ClientRuntime which is relying on System.Net.Http.HttpClient, HttpRequestMessage, etc.)
The question is, how do I define and implement the DocumentApi to transfer files to it in an efficient and generic way in the ApiController. Currently, I am thinking of three different options:
[HttpPut] public async Task<IHttpActionResult> StoreDocument(HttpPostedFileBase file)
[HttpPut] public async Task<IHttpActionResult> StoreDocument(byte[] fileContents, string contentType, string fileName)
[HttpPut] public async Task<IHttpActionResult> StoreDocument(Stream fileStream, string contentType, string fileName)
I was thinking, that maybe I could just forward the HttpPostedFileBase instance from the Mvc.Controller to the ApiController, so I've tried the first option. However, Add REST API Client... creates a DocumentApi.Models.HttpPostedFileBase model class in that case and is NOT of the original type System.Web.HttpPostedFileBase. Now, I'm not sure what I should do...
new DocumentApi.Models.HttpPostedFileBase() {
InputStream = file.InputStream,
ContentType = file.ContentType,
ContentLength = file.ContentLength,
FileName = file.FileName,
}
^ that doesn't work, because for the InputStream, it creates a DocumentApi.Models.Stream class and I have no idea how to convert the System.IO.Stream from the file parameter into a DocumentApi.Models.Stream.
Is it even possible to send a stream across ASP.NET MVC webservices? (Which would basically be the answer to option 3)
In my current state of knowledge, the only alternative appears to me being option 2, where I would send a byte-array containing the whole file. I am asking myself, however, if there is any more convenient or more efficient way to send a file from a Mvc.Controller to a remote ApiController. Is there?
Which one of the options would work and which one is the way to go?
Additionally, I have a bonus question to the above regarding HttpPostedFileBase: Is the way via HttpPostedFileBase the most efficient way to handle uploaded files in ASP.NET MVC? I have seen various alternatives:
HttpPostedFileBase like shown above in the first code sample
Using the raw HTTP-request via Request.Content.ReadAsStreamAsync() or something similar. Maybe the stream is more efficient for my use case?
Using JavaScript to encode a file into a Base64 string and send it via a hidden input field to the controller. (via FileReader's method readAsDataURL(file))
So many options... Which one is best/most generic/most efficient?
You should use the HttpClient lib in the calling controller to async upload the file to the destination controller. Various examples of using HttpClient to asycn upload is here C# HttpClient 4.5 multipart/form-data upload.
In this case your receiving remote controller just acts as though its having a file uploaded from a browser with no specific handling required.

Web Api 2 or Generic Handler to serve images?

I want to create an image handler, but i am torn between using Web API 2 or just a normal Generic Handler (ashx)
I have implemented both in the past, but which one is the most correct one.
I found an old SO post LINK but is it still really relevant?
WebApi is fully capable and I prefer it. The other answer is right that JSON and XML are default, but you can add your own MediaFormatter and serve any content type for any model. This allows you to perform content negotiation and provide different content based on the Accept header or file extension. Let's pretend our model is a "User". Imagine asking for a "User" as json, xml, jpg, pdf. With WebApi, we can use file extensions or the Accept header and ask for /Users/1 or Users/1.json for JSON, Users/1.jpg for jpg, Users/1.xml for xml, /Users/1.pdf for pdf, etc. All of these could also just be /Users/1 with different Accept headers with quality so your clients could ask for Users/1 with an Accept header asking for jpg first, but fall back to png.
Here is an example of how to create a formatter for .jpg.
public class JpegFormatter : MediaTypeFormatter
{
public JpegFormatter()
{
//this allows a route with extensions like /Users/1.jpg
this.AddUriPathExtensionMapping(".jpg", "image/jpeg");
//this allows a normal route like /Users/1 and an Accept header of image/jpeg
this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpeg"));
}
public override bool CanReadType(Type type)
{
//Can this formatter read binary jpg data?
//answer true or false here
return false;
}
public override bool CanWriteType(Type type)
{
//Can this formatter write jpg binary if for the given type?
//Check the type and answer. You could use the same formatter for many different types.
return type == typeof(User);
}
public override async Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content,
TransportContext transportContext)
{
//value will be whatever model was returned from your controller
//you may need to check data here to know what jpg to get
var user = value as User;
if (null == user)
{
throw new NotFoundException();
}
var stream = SomeMethodToGetYourStream(user);
await stream.CopyToAsync(writeStream);
}
}
Now we need to register our formatter (typically App_Start/WebApiConfig.cs)
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
...
//typical route configs to allow file extensions
config.Routes.MapHttpRoute("ext", "{controller}/{id}.{ext}");
config.Routes.MapHttpRoute("default", "{controller}/{id}", new { id = RouteParameter.Optional });
//remove any default formatters such as xml
config.Formatters.Clear();
//order of formatters matter!
//let's put JSON in as the default first
config.Formatters.Add(new JsonMediaTypeFormatter());
//now we add our custom formatter
config.Formatters.Add(new JpegFormatter());
}
}
And finally, our controller
public class UsersController : ApiController
{
public IHttpActionResult Get(int id)
{
var user = SomeMethodToGetUsersById(id);
return this.Ok(user);
}
}
Your controller will not have to change as you add different formatters. It simply returns your model and then formatters kick in later in the pipeline. I love formatters as it provides such a rich api. You can read more about formatters on the WebApi website.
The correct one is ashx the reason is the content type. If you use Web Api the content type a.k.a media formatter(format) of your response is the one defined for all your services meaning JSON, XML or oData.
//Global Asax, Web Api register methods is used to defined WebApi formatters
config.Formatters.Insert(0, new System.Net.Http.Formatting.JsonMediaTypeFormatter());
However an image is a binary therefore you need to send the image in its original format and not as JSON or XML
response.AddHeader("content-type", "image/png");
response.BinaryWrite(imageContent);
response.Flush();
That is why the ashx is the right tool for that job.
One additional advantage is that you have more control over your output without the need to code a new "formatter"(the way to make WebApi to resolve this issue) for each image type that you want to return by doing something like in Ashx file:
var png = Image.FromFile("some.png");
png.Save("a.gif", var png = Image.FromFile("some.png");
png.Save("a.gif", ImageFormat.Gif); //Note you can save the new image into a MemoryStream to return it later in the same method.
And you have wide variety of types to play with:
https://msdn.microsoft.com/en-us/library/system.drawing.imaging.imageformat(v=vs.110).aspx
Important: It is clear for all of us that both WebApi and Ashx can return images. I am not saying in any way that you can't achieve this with WebApi all I am saying is why I believe Ashx is the right choice.
I am a big fan of flexibility and customization. I personally would go for httpHandler. So to answer your question, it really depends on your requirements.
First thing WebAPI is the result of evolution all the way from http (GET/POST) calls to web services and the need of being able to transfer data in less cost as opposed to web services. HttpHandlers utilized same concept long time before web apis. Basically web apis are nothing but a http page without its user interface (if you want to).
Few things need to know before choosing HttpHandler or Web Api
development time - whether you know both very well and what will take less time for you to implement
As ManOVision mentioned in his answer, the routing user/1 you can still implement with simple code and httphandler so you don't really need Web API for that. only you may have to right the code manually adding up a little time in development
Flexibility, I personally like this part since I will be able to control the response content type as I want depending upon the data user provides. I don't need to perform session checks and redirect to any page. I can still control this behavior to whatever I want. web API also you can do but mostly things are taken at run time by itself to the configuration we put in place at the development.
rendering web page is the same for both. Although httphandler being easy so why to use Web API?
There might more comparison (As a manager I think from management side as well and not completely technical perspective) so you may have to weigh your options and decide what to do. Since handler file is anyway foundation of web API, I would say it gives more power to developer than web api does. just like a http socket would do more than httphandler.

In .NET MVC 4 (Web API), how do I intercept a request for a controller and change it's Content-Type?

Thanks for looking!
Background
I am writing an API layer for a company that will be used by disparate apps and external consumers.
On the consumption end, most consumers will call a service via ajax using a jQuery $.post(); however, Internet Explorer makes our lives more challenging (of course!). For IE, I must use the XDomainRequest object because IE will not run a jQuery $.post() and because if I use IE's XMLHttpRequest(), I get a security message which is unacceptable (again--of course!):
Otherwise, XMLHttpRequest() works fine.
I am using C#, .NET MVC 4 (WebApi)
Problem
The problem is that XDomainRequest does not allow you to set the Content-Type header and always defaults to text-plain which MVC 4 WebApi controllers will not accept (and yet again--of course!!).
Question
How can I intercept requests for my controllers, detect the presence of text-plain content types and change them to text-json content-type on the fly?
Thanks in advance!
Well after two days and pouring over documentation and reading in this thread I've been able to make this work. So please forgive me if my description of the solution is poor; this is the first time I answer one of these types of threads. Since it took me so long to find the problem I figured it is worth saving some soul from falling into this same problem.
The source for my help came from the above link by byterot.
First thing I did was to create a DelegatingHandler. So in my helper folder or where every you want to create a class and call it what you want.
Here is mine:
public class ContentTypeHandler : DelegatingHandler
{
/** Check that this is an IE browser. */
if ((request.Headers.UserAgent.ToString().IndexOf("MSIE", System.StringComparison.Ordinal) > -1))
{
MediaTypeHeaderValue contentTypeValue;
if (MediaTypeHeaderValue.TryParse("application/json", out contentTypeValue))
{
request.Content.Headers.ContentType = contentTypeValue;
request.Content.Headers.ContentType.CharSet = "utf-8";
}
}
/** Return request to flow. */
return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
// work on the response
var response = task.Result;
return response;
});
}
Last think that you have to do is call the Handler in your Global.asax.cs file inside your Application_Start():
GlobalConfiguration.Configuration.MessageHandlers.Add(new ContentTypeHandler());
That's all I did and it worked. So good luck I hope this helps someone.
There is no problem modifying request in HTTP stack. It can be done by writing and registering your custom DelegatingHandler before it gets to the controller. Delegating handler can take care of this early-on in the game, so your request will arrive to the controller in the form you want it to. It could be route-specific handler as well.
http://msdn.microsoft.com/en-us/library/system.net.http.delegatinghandler.aspx
Did you try $.ajax instead of $.post ?

Replacing legacy system & creating new server code using ServiceStack + custom serialization

We have a legacy server code that we want to abandon and develop new one using ServiceStack. Existing clients are not written in .Net. We don't plan to use .Net on the client side at all.
Data between client and server is being exchanged using XML and JSON - at the moment JSON is only used as a return format for the response (just for some of the services available). XML format was defined when the first version of the server solution was created couple of years ago. We don't want to change it.
How do we use ServiceStack to build new RESTful webservices, that will serialize and deserialize data to a format that was designed in the past (please note, that clients will not be written in C#/.Net). We need to contol both: serialization & deserialization. Is that possible to use DTOs and still have control on how are these objects serialized / deserialized?
Adding custom logic via Request / Response Filters
See Request and response filters to see how to add custom logic before and after your service is called. It's best to add these filters via the Request / Response FilterAttributes as it allows you mark only the services that need these filters applied.
The problem with the Request Filter is it happens after the deserialization into the request DTO which is too late to add custom de-serialization logic. To get around this you can register a custom Request binder in your AppHost with:
base.RegisterRequestBinder<MyRequest>(httpReq => ... requestDto);
This gives you access to the IHttpRequest object and lets you add the custom deserialization logic yourself. The other option is to tell ServiceStack to not attempt to deserialize the request itself and instead inject the HttpRequest InputStream so you can deserialize the request yourself:
public class Hello : IRequiresRequestStream {
Stream RequestStream { get; set; }
}
Both these examples are explained on ServiceStack's Serialization and De-Serialization wiki page.
Registering your own Custom Media Type
Another option to be able to return strong-typed DTOs but change the output for certain requests can be done by adding a new custom media type as explained in the Northwind VCard Custom media type example, e.g:
public static void Register(IAppHost appHost)
{
appHost.ContentTypeFilters.Register( "text/x-vcard", SerializeToStream, DeserializeFromStream);
}
...
public static void SerializeToStream(IRequestContext requestContext, object response, Stream stream)
{
var customerDetailsResponse = response as CustomerDetailsResponse;
using (var sw = new StreamWriter(stream))
{
if (customerDetailsResponse != null)
{
WriteCustomer(sw, customerDetailsResponse.Customer);
}
var customers = response as CustomersResponse;
if (customers != null)
{
customers.Customers.ForEach(x => WriteCustomer(sw, x));
}
}
}
This is a good option if you can mount the custom XML responses under a different Content Type, e.g. application/v-xml so it doesn't conflict with the existing XML format/endpoint. Using the ContentType above your HTTP Client can call this custom implementation with ?format=v-xml or using the HTTP Header: Accept: application/v-xml.
If you want to override the built-in XML ContentType you still can but I recommend falling back to the original XmlSerializer implementation for the SerializeStream and DeserializeStream methods if it's not one of the legacy formats you have to support.
By-pass ServiceStack and execute using your own Custom IHttpHandler
Another option is to by-pass ServiceStack completely and instead process the request in your own custom IHttpRequest handler by registering it in ServiceStack's config in your AppHost:
SetConfig(new EndpointHostConfig {
RawHttpHandlers = {
httpReq => return IsLegacyMatch(httpReq) ? new LegacyXmlHandler() : null
}
});
Returning non-null (i.e. any handler) by-passes ServiceStack.

Categories

Resources