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.
Related
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);
}
}
In ASP.NET Web API, I need to force XML output for a single method but leave the JSON formatter enabled for others. Everything that I have seen on the topic has suggested removing the JSON formatter from the GlobalConfiguration as follows:
// remove JSON formatter
var formatters = GlobalConfiguration.Configuration.Formatters;
formatters.Remove(formatters.JsonFormatter);
This works but disables JSON output application wide. I need to be able to specify a formatter for a specific method or controller without affecting the global configuration. Is this possible or can it only be done via the GlobalConfiguration?
Microsoft introduced per-controller configuration for this specific purpose. You will need to split up your functionality into different controllers, but hopefully that will not be too much of a hassle for your specific goal (it might even be an improvement).
Basically, here is what you do:
Setup basic JSON formatting for the general case
Introduce a specific Controller for the XML methods, and give it a specific configuration:
[XMLControllerConfig]
public class XMLController: ApiController
{
[HttpGet]
public string SomeMethod(string someArgument)
{
return "abc";
}
}
...
class XMLControllerConfigAttribute: Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings,
HttpControllerDescriptor controllerDescriptor)
{
controllerSettings.Formatters.Clear();
controllerSettings.Formatters.Add(new XMLFormatter());
}
}
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/
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.
In my current project we have a notification system. When an oject is added to another objects collection, an email is sent to those who are subscibed to the parent object. This happens on the object layer and not in the View or Controller.
Here's the problem:
Although we can say who created what with what information in the email, we cannot embed links to those objects in the email because in the object layer there is no access to a UrlHelper. To construct a UrlHelper you need a RequestContext, which again does not exist on the object layer.
Question:
I want to make a helper class to create the url's for me. How can I create an object that will generate these urls without a request context? Is it possible?
The problem is compounded by the fact that you don't want a relative URL in an email, you want an absolute email so you need to hard-code the domain too because there is no request to grab it from.
Another factor is that emails can outlive the current site structure by months or years so you need a kind of permalink, and thus a way to associate multiple Urls with a single action (additional routes). This latter issue is also a factor in SEO where you don't want to leave any page behind.
For now a static method on your controller UrlToActionX(params) sitting next to the method ActionX seems like the simplest workaround. All it does is the appropriate string.Format(...) on the id's of the strongly-typed parameters to generate the permanent Url. Add a static domain on the front, or a domain from the user object (since you know which domain they visit when they come to your site) and you have your email link.
It's not ideal but at least you now have only one place to maintain the Url generation.
IMHO: When it comes to permanent links to a changing web site sometimes it's better to rely on "configuration over convention". :-)
I'm not aware of a way to do this, you MUST have access to the routes at the very least to make your own helper. Unless your business objects know about the registered routes, you can't get away from doing some hard-coding.
Here is how you might limit the hard-coding of urls though...
Code in a url with all the relevant bits in your object's methods..
class Event
{
public void SendEmail()
{
var url = string.Format("http://myurl.com/r/Event?eventId={0}", EventId);
//send emails...
}
}
Note the /r/Event piece of the url. This would be a map to a RController that would be responsible for taking arbitrary, made-up links and sending a 301 Permanent Redirect and going through the route engine to create a real url using the current routes. This way you are only hard-coding a utility controller url and not to the ever evolving controller actions of your real pages.
class RController : Controller
{
public ActionResult Event(int eventId)
{
Response.StatusCode = (int)HttpStatusCode.MovedPermanently;
Response.RedirectLocation = Url.Action("Details", "Event", new { eventId = eventId });
return null;
}
public ActionResult Register(int eventId)
{
Response.StatusCode = (int)HttpStatusCode.MovedPermanently;
Response.RedirectLocation = Url.Action("Register", "Event", new { eventId = eventId });
return null;
}
}
It just feels a bit better than hard-coding a bunch of different controllers/actions that you might decide to rename later. Think of it as your own little TinyUrl like service.
You could define an interface with a method that takes whatever information is necessary to create a URL (object ids or whatever) and returns a URL. Write an implementation of that interface that uses the UrlHelper to do this work, and then supply this to your object layer (ideally with an IoC container).
You could use:
VirtualPathUtility.ToAbsolute(string.Format("~/r/Event?eventId={0}", id))
to resolve the url. Still not nice though.