I need to somehow attach my custom data to the HttpRequest being handled by my IIS custom modules - so that code that runs in earlier stages of IIS pipeline attaches an object and code that runs in later stages can retrieve the object and use it and no other functionality of IIS pipeline processing is altered by adding that object.
The data needs to persist within one HTTP request only - I don't need it to be stored between requests. I need it to be "reset" for each new request automatically - so that when a new request comes it doesn't contain objects my code attached to the previous request.
Looks like HttpContext.Items is the way to go, although MSDN description of its purpose is not very clear.
Is using HttpContext.Current.Items the way to solve my problem?
This should work - I have done this in a project before.
I have a class which has a static property like this -
public class AppManager
{
public static RequestObject RequestObject
{
get
{
if (HttpContext.Current.Items["RequestObject"] == null)
{
HttpContext.Current.Items["RequestObject"] = new RequestObject();
}
return (RequestObject)HttpContext.Current.Items["RequestObject"];
}
set { HttpContext.Current.Items["RequestObject"] = value; }
}
}
And then RequestObject contains all my custom data so then in my app I can do
AppManager.RequestObject.CustomProperty
So far I have not come across any issues in the way HttpContext.Items works.
Related
I'm currently working on a webserver in asp.net core.
I want the server to process the users input and data and am looking for a good solution to save complex Objects for the runtime.
So my first approach was to use Sessions. In Asp.net, sessions used to work like Session["key"] = new ValueObject()
In asp.net core however you can only use the methods SetString, SetInt32 and Set for byte arrays. I found a lot of solutions which basically converted the objects into Json strings. However in my case this isn't possible due to the objects containing other object references and more.
My second idea was to create a list of objects with the SessionId as identifier. Problem with this is that every time I would make request to the server, it needs to go through all existing Sessions to find the matching one, so this would probably drastically increase the time for the request.
So my question is what would be the best way to save user related objects?
Is using Sessions even the best way for solving this problem or am I missing something?
Note: Request are handled by JQuery AJAX, so reloading the page for accessing data is not an option.
You could try using the MemoryCache that can hold any .net type. It is not a problem but given it is a shared structure, it will be shared to all users, so, you have to carefull manage it. To do it, you could use HttpContext.Session.Id to define the keys on the memory cache instance. For sample (pseudo-code I didn't test):
public class HomeController : Controller
{
private IMemoryCache _cache;
public HomeController(IMemoryCache memoryCache)
{
_cache = memoryCache;
}
public async Task<IActionResult> CacheGetOrCreateAsynchronous()
{
string cacheKey = $"{HttpContext.Session.Id}_data";
var cacheEntry = await
_cache.GetOrCreateAsync(cacheKey , entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(3);
return Task.FromResult(DateTime.Now);
});
return View("Cache", cacheEntry);
}
}
I'm working on a ASP.NET Core MVC web app. I have a Model that includes a Dictionary. In one Action I'm adding a new element to it. Then I have other actions supposed to use the object from the Dictionary that was just added. But as it turns out - the dictionary is empty after the first action finished executing. Is there a way to fix it, so that the object is added permanently to the dictionary?
Update:
Well, the object I need to store is basically a virtual medical slide with a Deep Zoom tile generator. The flow is as follows: user click on the link to open the slide -> the ViewSlide Action creates the slide object -> then the OpenSeadragon viewer on the corresponding view sends requests to get XML metadata and JPEG tiles (256x256) on various Deep Zoom levels (based on mouse cursor position). So there's going to be a lot of requests for the tiles and I'm looking for a way to optimize the time needed to create them.
Here's a code snippet of the said actions:
[Route("[controller]/{slug}")]
public IActionResult ViewSlide(string slug)
{
try
{
var currentSlide = slideSet.Get(slug);
return View(currentSlide);
}
catch (Exception)
{
return RedirectToAction("Index");
}
}
public Slide Get(string slideUrl)
{
if (Slides.ContainsKey(slideUrl))
return Slides[slideUrl];
var pathToSlide = FilePaths[slideUrl];
Slides[slideUrl] = new Slide(pathToSlide);
return Slides[slideUrl];
}
[Produces("application/xml")]
[Route("[controller]/{slug}.dzi")]
public string Dzi(string slug)
{
try
{
return slideSet.Get(slug).DeepZoomGenerator.GetDziMetadataString(DEEPZOOM_FORMAT);
}
catch (Exception)
{
RedirectToAction("Index");
return "";
}
}
If you want to add the item permanently you can store it in:
Session (will not work in a web farm)
Cookie
Database
File
Here is how to store it in session:
// Place something in session
System.Web.HttpContext.Current.Session["whatever"] = value;
// Read from session
var whatever = System.Web.HttpContext.Current.Session["whatever"];
MVC also provides TempData which is basically a session which lives during the lifecycle of the trip on the server.
Depending on how you want to use this data, you have different options:
You can store it in the Session, Cookie, or TempData, if it's tied to the client, and no one else will need it. How long do you want to store the data? Cookies can be cleared, and you don't want to hold too much data in the Session either for a long time.
If the data does not belong to specific users, you can use a repository (e.g. singleton dictionary / database / HttpCache), but the first two needs to be cleaned regularly, while the HttpCache is not guaranteed to hold the data until it's requested.
And you could also rethink this concept, and stay stateless. This also makes it easier to scale your application horizontally, as well as adding HTTP cache, or even reverse proxy.
So basically it depends on what kind of data would you like to persist between action calls.
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);
}
}
I am experiencing a problem with the cacheManager in NopCommerce
I have a list of nopCommerce products, in the form of an IPagedList<Product>
I add them to my cachemanager as such:
_cacheManager.Set("SearchResult", products.ToList(), 5);
Now whenever i try to retrieve them like this:
var searchresults = new List<Product>();
if (_cacheManager.IsSet("SearchResult"))
{
searchresults = _cacheManager.Get<List<Product>>("SearchResult");
}
It is just empty, like, the isSet evaluates to false.
I tried to _cacheManager.Clear() before i add them, but that also doesn't work. I am running out of ideas here. Anyone got a clue?
I used this as a source for the retrieval:
http://www.nopcommerce.com/boards/t/12290/getting-an-item-on-the-cachemanager-requires-a-function-param-.aspx
I suppose that the problem is that you can't cache data between http requests, but I'm sure that you can retrieve that data during the same request.
NopCommerce has two cache managers. Both are declared at DependencyRegistrar.cs:
builder.RegisterType<MemoryCacheManager>().As<ICacheManager>().Named<ICacheManager>("nop_cache_static").SingleInstance();
builder.RegisterType<PerRequestCacheManager>().As<ICacheManager>().Named<ICacheManager>("nop_cache_per_request").InstancePerHttpRequest();
The default cache manager, only holds data for the current HTTP request. The second one, the static cache, spans to other HTTP requests.
If you want to use the static cache, you need to instruct Autofac to inject it when you configure the dependencies for your service. Look at DependencyRegistrar.cs, there are several examples for this, for instance:
builder.RegisterType<ProductTagService>().As<IProductTagService>()
.WithParameter(ResolvedParameter.ForNamed<ICacheManager>("nop_cache_static"))
.InstancePerHttpRequest();
I encourage you to use this approach instead of adding an static reference to MemoryCacheManager.
I solved it by adding this._cacheManager = new MemoryCacheManager() instead of
this._cacheManager = cacheManager, where cachemanager is an instance of ICacheManager
So we have a webservice that is called from different applications and it runs an extraction of data which takes a while and we don't want it to run multiple times. So we thought we could set an HttpContext.Current.Application["isRunning"] to be persistent through all the requests like :
if ((bool)HttpContext.Current.Application["isRunning"])
And it doesn't work, since a new HttpContext is created when an other application call the webmethod.
Except writing onto the disk or in AppSettings I don't see how I can persist data through every request to only have one instance of my webmethod running at a time. I've tried with Application, Cache and static variables but they all do not persist across requests. It seems it creates a new instance each time.
Preventing a new instance to be created or persist data through instances would fix the issue. Any hint?
You could use EnterpriseLibraries Caching Block to cache the data following extraction.
http://www.codeproject.com/KB/web-cache/CachingApplicationBlock.aspx
Once you have the Enterprise Library assemblies referenced, it's just a case of adding a few lines to your web.config and then using code such as the following inside your service.
//Create Instance of CacheManager
ICacheManager _objCacheManager = CacheFactory.GetCacheManager();
AbsoluteTime timeToExpire = new AbsoluteTime(TimeSpan.FromMinutes(60));
MyData myData = null;
myData = (MyData)cacheManager.GetData("ref");
if (myData == null)
{
//get the data
cacheManager.Add("ref", myData, CacheItemPriority.Normal, null, timeToExpire);
}
return myData;
Take a look at the following links, which provide useful information on wanting to use the Singleton pattern with web services.
http://forums.asp.net/t/881617.aspx/1
http://social.msdn.microsoft.com/Forums/en-US/asmxandxml/thread/72274741-dbbe-4a64-a360-6bbe60026ec9/
http://msdn.microsoft.com/en-us/library/ff650316.aspx