How to programmatically clear outputcache for controller action method - asp.net

If the controller action has the OutputCache attribute specified on an action, is there any way to clear the output cache without having to restart IIS?
[OutputCache (Duration=3600,VaryByParam="param1;param2")]
public string AjaxHtmlOutputMethod(string param1, string param2)
{
var someModel = SomeModel.Find( param1, param2 );
//set up ViewData
...
return RenderToString( "ViewName", someModel );
}
I'm looking at using HttpResponse.RemoveOutputCacheItem(string path) to clear it, but I'm having trouble figuring out what the path should be to map it to the action method. I'm going to try again with the aspx page that is rendered by ViewName.
Possibly I'll just manually insert the output of RenderToString into the HttpContext.Cache instead if I can't figure this one out.
Update
Please note that the OutputCache is VaryByParam, and testing out a hardcoded path "/controller/action" does not actually clear the outputcache, so it looks like it has to match "/controller/action/param1/param2".
That means I'll probably have to revert to object level caching and manually cache the output for RenderToString() :(

Try this
var urlToRemove = Url.Action("AjaxHtmlOutputMethod", "Controller");
HttpResponse.RemoveOutputCacheItem(urlToRemove);
UPDATED:
var requestContext = new System.Web.Routing.RequestContext(
new HttpContextWrapper(System.Web.HttpContext.Current),
new System.Web.Routing.RouteData());
var Url = new System.Web.Mvc.UrlHelper(requestContext);
UPDATED:
Try this:
[OutputCache(Location= System.Web.UI.OutputCacheLocation.Server, Duration=3600,VaryByParam="param1;param2")]
Otherwise the cache deletion won't work because you've
cached the HTML output on the user's machine

Further to the accepted answer, to support VaryByParam parameters:
[OutputCache (Duration=3600, VaryByParam="param1;param2", Location = OutputCacheLocation.Server)]
public string AjaxHtmlOutputMethod(string param1, string param2)
{
object routeValues = new { param1 = param1, param2 = param2 };
string url = Url.Action("AjaxHtmlOutputMethod", "Controller", routeValues);
Response.RemoveOutputCacheItem(url);
}
However Egor's answer is much better, because it supports all OutputCacheLocation values:
[OutputCache (Duration=3600, VaryByParam="param1;param2")]
public string AjaxHtmlOutputMethod(string param1, string param2)
{
if (error)
{
Response.Cache.SetNoStore();
Response.Cache.SetNoServerCaching();
}
}
When SetNoStore() and SetNoServerCaching() are called, they prevent the current Request being cached. Further requests will be cached, unless the functions are called for those requests as well.
This is ideal for handling error situations - when normally you want to cache responses, but not if they contain error messages.

I think correct flow is:
filterContext.HttpContext.Response.Cache.SetNoStore()

Add code to AjaxHtmlOutputMethod
HttpContext.Cache.Insert("Page", 1);
Response.AddCacheItemDependency("Page");
To clear output cache you can now use
HttpContext.Cache.Remove("Page");

Another option is to use VaryByCustom for the OutputCache and handle the invalidation of certain cache elements there.
Maybe it works for you, but it's not a general solution to your problem

Related

Url helper for full url in asp.net mvc-3

Writing
#Url.Content("~/Something/Something.html")
in razor renders
/AppFolder/Something/Something.html
Is there a way to render the full URL like http://www.something.com/AppFolder/Something/Something.html without atrocious hacks? (like storing the protocol and domain in the AppConfig, and concatenate the string to it)
Is there a helper like #Url.FullPath("~/asdf/asdf") or similar?
See this blog post for the answer.
Basically, all you need to do it include the protocol parameter e.g.
Url.Action("About", "Home", null, "http")
The #Url.RouteURL() does not quiet answer this question. It does work for named routes but falls short for arbitrary virtual paths.
Here is quick helper method that generates full outbound url. You can create overloads for various schemes (http[s]) depending on the degree of control desired.
public static class UrlHelperExtension
{
public static string ContentFullPath(this UrlHelper url,string virtualPath)
{
var result = string.Empty;
Uri requestUrl = url.RequestContext.HttpContext.Request.Url;
result = string.Format("{0}://{1}{2}",
requestUrl.Scheme,
requestUrl.Authority,
VirtualPathUtility.ToAbsolute(virtualPath));
return result;
}
}
For anyone needing to build URLs in WebAPI 2.2 and/or MVC5, this worked for me:
// works in a controller
var requestUri = this.Request.RequestUri;
// just the http/s and the hostname; ymmv
string baseUrl = requestUri.Scheme + "://" + requestUri.Authority + "/";
// build your url for whatever purpose you need it for
string url = baseUrl + "SomeOtherController?id=" + <some_magic_value>;
You can use a helper to produce a full url, including protocol. Note the first lowercase in url.Action.
var url = new UrlHelper(System.Web.HttpContext.Current.Request.RequestContext);
var fullUrl = url.Action("YourAction", "YourController", new { id = something }, protocol: System.Web.HttpContext.Current.Request.Url.Scheme);
Output
https://www.yourdomain.com/YourController/YourAction?id=something

ASP.NET localization

When localizing an ASP.NET app (MVC or webforms, does't matter), how do you handle HTML strings in your resource file? In particular, how do you handle something like a paragraph with an embedded dynamic link? My strategy so far has been to use some sort of placeholder for the href attribute value and replace it at runtime with the actual URL, but this seems hokey at best.
As an example, suppose my copy is:
Thank you for registering. Click
here
to update your preferences.
To login and begin using the app, click
here.
Using MVC (Razor), what could be a simple:
<p>#Resources.Strings.ThankYouMessage</p>
now turns into
<p>#Resources.Strings.ThankYouMessage
.Replace("{prefs_url}", Url.Action("Preferences", "User"))
.Replace("{login_url}", Url.Action("Login", "User"))</p>
It's not horrible, but I guess I'm just wondering if there's a better way?
There isn't really a better way, beyond some syntax and performance tweaks. For example, you might add a cache layer so that you aren't doing these string operations for every request. Something like this:
<p>#Resources.LocalizedStrings.ThankYouMessage</p>
which calls a function perhaps like this:
Localize("ThankYouMessage", Resources.Strings.ThankYouMessage)
which does a hashtable lookup by resource + culture:
//use Hashtable instead of Dictionary<> because DictionaryBase is not thread safe.
private static System.Collections.Hashtable _cache =
System.Collections.Hashtable.Synchronized(new Hashtable());
public static string Localize(string resourceName, string resourceContent) {
string cultureName = System.Threading.Thread.CurrentThread.CurrentCulture.Name;
if (string.IsNullOrEmpty(resourceName))
throw new ArgumentException("'resourceName' is null or empty.");
string cacheKey = resourceName + "/" + cultureName;
object o = _cache[cacheKey];
if (null == o) { //first generation; add it to the cache.
_cache[cacheKey] = o = ReplaceTokensWithValues(resourceContent);
}
return o as string;
}
Notice the call to ReplaceTokensWithValues(). That is the function that contains all the "not horrible" string-replacement fiffery:
public static string ReplaceTokensWithValues(string s) {
return s.Replace("{prefs_url}", Url.Action("Preferences", "User"))
.Replace("{login_url}", Url.Action("Login", "User")
.Replace("{any_other_stuff}", "random stuff");
}
By using a caching approach as above, ReplaceTokensWithValues() is only called once per culture, per resource for the lifetime of the application--instead of once per resource call. The difference may be on the order of 100 vs. 1,000,000.

How can I access session variables when the page is loaded using a SimpleWorkerRequest?

I'm reading an ASPX file as a string and using the returned HTML as the source for an email message. This is the code:
public string GetEmailHTML(int itemId)
{
string pageUrl = "HTMLEmail.aspx";
StringWriter stringWriter = new StringWriter();
HttpRuntime.ProcessRequest(new SimpleWorkerRequest(pageUrl, "ItemId=" + itemId.ToString(), stringWriter));
stringWriter.Flush();
stringWriter.Close();
return stringWriter.ToString();
}
HTMLEmail.aspx uses the ItemId query string variable to load data from a DB and populate the page with results. I need to secure the HTMLEmail.aspx page so a manipulated query string isn't going to allow just anybody to see the results.
I store the current user like this:
public User AuthenticatedUser
{
get { return Session["User"] as User; }
set { Session["User"] = value; }
}
Because the page request isn't made directly by the browser, but rather the SimpleWorkerRequest, there is no posted SessionId and therefore HTMLEmail.aspx cannot access any session variables. At least, I think that's the problem.
I've read the overview on session variables here: http://msdn.microsoft.com/en-us/library/ms178581.aspx
I'm wondering if I need to implement a custom session identifier. I can get the current SessionId inside the GetEmailHTML method and pass it as a query string param into HTMLEmail.aspx. If I have the SessionId inside HTMLEmail.aspx I could maybe use the custom session identifier to get access to the session variables.
That fix sounds messy. It also removes the encryption layer ASP automatically applies to the SessionId.
Anyone have a better idea?
As far as I can see, your best bet is to pass on all the values you need inside HTMLEmail.aspx to it via the query parameters, just like you do with ItemId.
Apart from that, you can probably get away with just sending in the UserId of the user to that page and make it hit the DB (or wherever you are storing your users) to the User object, instead of trying to read it off the Session variables.
Edit:
Why don't you use:
public string GetEmailHTML(int itemId)
{
string pageUrl = "HTMLEmail.aspx";
StringWriter stringWriter = new StringWriter();
Server.Execute(pageUrl, stringWriter);
stringWriter.Flush();
stringWriter.Close();
return stringWriter.ToString();
}
instead? As far as I can see Server.Execute inherits the same http request.

Change Single URL query string value

I have an ASP.NET page which takes a number of parameters in the query string:
search.aspx?q=123&source=WebSearch
This would display the first page of search results. Now within the rendering of that page, I want to display a set of links that allow the user to jump to different pages within the search results. I can do this simply by append &page=1 or &page=2 etc.
Where it gets complicated is that I want to preserve the input query string from the original page for every parameter except the one that I'm trying to change. There may be other parameters in the url used by other components and the value I'm trying to replace may or may not already be defined:
search.aspx?q=123&source=WebSearch&page=1&Theme=Blue
In this case to generate a link to the next page of results, I want to change page=1 to page=2 while leaving the rest of the query string unchanged.
Is there a builtin way to do this, or do I need to do all of the string parsing/recombining manually?
You can't modify the QueryString directly as it is readonly. You will need to get the values, modify them, then put them back together. Try this:
var nameValues = HttpUtility.ParseQueryString(Request.QueryString.ToString());
nameValues.Set("page", "2");
string url = Request.Url.AbsolutePath;
string updatedQueryString = "?" + nameValues.ToString();
Response.Redirect(url + updatedQueryString);
The ParseQueryString method returns a NameValueCollection (actually it really returns a HttpValueCollection which encodes the results, as I mention in an answer to another question). You can then use the Set method to update a value. You can also use the Add method to add a new one, or Remove to remove a value. Finally, calling ToString() on the name NameValueCollection returns the name value pairs in a name1=value1&name2=value2 querystring ready format. Once you have that append it to the URL and redirect.
Alternately, you can add a new key, or modify an existing one, using the indexer:
nameValues["temp"] = "hello!"; // add "temp" if it didn't exist
nameValues["temp"] = "hello, world!"; // overwrite "temp"
nameValues.Remove("temp"); // can't remove via indexer
You may need to add a using System.Collections.Specialized; to make use of the NameValueCollection class.
You can do this without all the overhead of redirection (which is not inconsiderable). My personal preference is to work with a NameValueCollection which a querystring really is, but using reflection:
// reflect to readonly property
PropertyInfo isReadOnly = typeof(System.Collections.Specialized.NameValueCollection).GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);
// make collection editable
isReadOnly.SetValue(this.Request.QueryString, false, null);
// remove
this.Request.QueryString.Remove("foo");
// modify
this.Request.QueryString.Set("bar", "123");
// make collection readonly again
isReadOnly.SetValue(this.Request.QueryString, true, null);
Using this QueryStringBuilder helper class, you can grab the current QueryString and call the Add method to change an existing key/value pair...
//before: "?id=123&page=1&sessionId=ABC"
string newQueryString = QueryString.Current.Add("page", "2");
//after: "?id=123&page=2&sessionId=ABC"
Use the URIBuilder Specifically the link textQuery property
I believe that does what you need.
This is pretty arbitrary, in .NET Core at least. And it all boils down to asp-all-route-data
Consider the following trivial example (taken from the "paginator" view model I use in virtually every project):
public class SomeViewModel
{
public Dictionary<string, string> NextPageLink(IQueryCollection query)
{
/*
* NOTE: how you derive the "2" is fully up to you
*/
return ParseQueryCollection(query, "page", "2");
}
Dictionary<string, string> ParseQueryCollection(IQueryCollection query, string replacementKey, string replacementValue)
{
var dict = new Dictionary<string, string>()
{
{ replacementKey, replacementValue }
};
foreach (var q in query)
{
if (!string.Equals(q.Key, replacementKey, StringComparison.OrdinalIgnoreCase))
{
dict.Add(q.Key, q.Value);
}
}
return dict;
}
}
Then to use in your view, simply pass the method the current request query collection from Context.Request:
<a asp-all-route-data="#Model.NextPageLink(Context.Request.Query)">Next</a>

Asp.Net single control render for AJAX calls

I'm trying to implement something similar to this or this.
I've created a user control, a web service and a web method to return the rendered html of the control, executing the ajax calls via jQuery.
All works fine, but if I put something in the user control that uses a relative path (in my case an HyperLink with NavigateUrl="~/mypage.aspx") the resolution of relative path fails in my developing server.
I'm expecting:
http://localhost:999/MyApp/mypage.aspx
But I get:
http://localhost:999/mypage.aspx
Missing 'MyApp'...
I think the problem is on the creation of the Page used to load the control:
Page page = new Page();
Control control = page.LoadControl(userControlVirtualPath);
page.Controls.Add(control);
...
But I can't figure out why....
EDIT
Just for clarity
My user control is located at ~/ascx/mycontrol.ascx
and contains a really simple structure: by now just an hyperlink with NavigateUrl like "~/mypage.aspx".
And "mypage.aspx" really resides on the root.
Then I've made up a web service to return to ajax the partial rendered control:
[ScriptService]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class wsAsynch : System.Web.Services.WebService
{
[WebMethod(EnableSession = true)]
public string GetControl(int parma1, int param2)
{
/* ...do some stuff with params... */
Page pageHolder = new Page();
UserControl viewControl = (UserControl)pageHolder.LoadControl("~/ascx/mycontrol.ascx");
Type viewControlType = viewControl.GetType();
/* ...set control properties with reflection... */
pageHolder.Controls.Add(viewControl);
StringWriter output = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, output, false);
return output.ToString();
}
}
The html is correctly rendered, but the relative path in the NavigateUrl of hyperlink is incorrectly resolved, because when I execute the project from developing server of VS2008, the root of my application is
http://localhost:999/MyApp/
and it's fine, but the NavigateUrl is resolved as
http://localhost:999/mypage.aspx
losing /MyApp/ .
Of Course if I put my ascx in a real page, instead of the pageHolder instance used in the ws, all works fine.
Another strange thing is that if I set the hl.NavigateUrl = Page.ResolveUrl("~/mypage.aspx") I get the correct url of the page:
http://localhost:999/MyApp/mypage.aspx
And by now I'll do that, but I would understand WHY it doesn't work in the normal way.
Any idea?
The problem is that the Page-class is not intented for instantiating just like that. If we fire up Reflector we'll quickly see that the Asp.Net internals sets an important property after instantiating a Page class an returning it as a IHttpHandler. You would have to set AppRelativeTemplateSourceDirectory. This is a property that exists on the Control class and internally it sets the TemplateControlVirtualDirectory property which is used by for instance HyperLink to resolve the correct url for "~" in a link.
Its important that you set this value before calling the LoadControl method, since the value of AppRelativeTemplateSourceDirectory is passed on to the controls created by your "master" control.
How to obtain the correct value to set on your property? Use the static AppDomainAppVirtualPath on the HttpRuntime class. Soo, to sum it up... this should work;
[WebMethod(EnableSession = true)]
public string GetControl(int parma1, int param2)
{
/* ...do some stuff with params... */
var pageHolder = new Page() { AppRelativeTemplateSourceDirectory = HttpRuntime.AppDomainAppVirtualPath };
var viewControl = (UserControl)pageHolder.LoadControl("~/ascx/mycontrol.ascx");
var viewControlType = viewControl.GetType();
/* ...set control properties with reflection... */
pageHolder.Controls.Add(viewControl);
var output = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, output, false);
return output.ToString();
}
The tildy pust the path in the root of the app, so its going to produce a the results you are seeing. You will want to use:
NavigateUrl="./whatever.aspx"
EDIT:
Here is a link that may also prove helpful...http://msdn.microsoft.com/en-us/library/ms178116.aspx
I find the /MyApp/ root causes all sorts of issues. It doesn't really answer your question 'why is doesn't work the normal way', but do you realize you can get rid of the /MyApp/ and host your website at http:/localhost/...?
Just set Virtual Path in the website properties to '/'.
This clears everything up, unless of course you are trying to host multiple apps on the development PC at the same time.
It might be that the new page object does not have "MyApp" as root, so it is resolved to the server root as default.
My question is rather why it works with Page.ResolveUrl(...).
Maybe ResolveUrl does some more investigation about the location of the usercontrol, and resolves based on that.
Weird, I recreated the example. The hyperlink renders as <a id="ctl00_hlRawr" href="Default.aspx"></a> for a given navigation url of ~/Default.aspx. My guess is that it has something to do with the RequestMethod. On a regular page it is "GET" but on a webservice call it is a "POST".
I was unable to recreate your results with hl.NavigateUrl = Page.ResolveUrl("~/mypage.aspx")
The control always rendered as <a id="ctl00_hlRawr" href="Default.aspx"></a> given a virtual path. (Page.ResolveUrl gives me "~/Default.aspx")
I would suggest doing something like this to avoid the trouble in the future.
protected void Page_Load(object sender, EventArgs e)
{
hlRawr.NavigateUrl = FullyQualifiedApplicationPath + "/Default.aspx";
}
public static string FullyQualifiedApplicationPath
{
get
{
//Return variable declaration
string appPath = null;
//Getting the current context of HTTP request
HttpContext context = HttpContext.Current;
//Checking the current context content
if (context != null)
{
//Formatting the fully qualified website url/name
appPath = string.Format("{0}://{1}{2}{3}",
context.Request.Url.Scheme,
context.Request.Url.Host,
(context.Request.Url.Port == 80 ? string.Empty : ":" + context.Request.Url.Port),
context.Request.ApplicationPath);
}
return appPath;
}
}
Regards,
It is hard to tell what you are trying to achieve without posting the line that actually sets the Url on of the HyperLink, but I think I understand your directory structure.
However, I have never run into a situation that couldn't be solved one way or another with the ResolveUrl() method. String parsing for a temporary path that won't be used in production is not recommended because it will add more complexity to your project.
This code will resolve in any object that inherits from page (including a usercontrol):
Page page = (Page)Context.Handler;
string Url = page.ResolveUrl("~/Anything.aspx");
Another thing you could try is something like this:
Me.Parent.ResolveUrl("~/Anything.aspx");
If these aren't working, you may want to check your IIS settings to make sure your site is configured as an application.

Resources