Using Output Cache in MVC for Object Parameter - asp.net-mvc

This is my controller method. Can anyone explain how I could write outputcache for the following method on the server.
public JsonResult GetCenterByStateCityName(string name, string state, string city, bool sportOnly, bool rvpOnly)
{
var result = GetCenterServiceClient().GetCentersByLocation(name, city, state, sportOnly, rvpOnly).OrderBy(c => c.Name).ToList();
return Json(result);
}
Thank you

Have you looked at the documentation?
http://msdn.microsoft.com/en-us/library/system.web.mvc.outputcacheattribute.aspx
In a nutshell, just set the Attribute on your Action
[OutputCache(CacheProfile = "SaveContactProfile", Duration = 10)]
public JsonResult SaveContact(Contact contact)
{
var result = GetContactServiceClient().SaveContact(contact);
return Json(result);
}
-- UPDATE --
If you're making a direct Ajax call via jQuery, the OutPutCache could be ignored based on the "cache" parameter - which is set to true by default.
For instance, your parameter would be ignored if you're doing something like:
$.ajax({
url: someUrlVar,
cache: true, /* this is true by default */
success : function(data) {
}
});
Just something to look at as you can cache that call two ways.
Reference:
http://api.jquery.com/jQuery.ajax/
http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/improving-performance-with-output-caching-cs

[OutputCache(Duration = 3600, VaryByParam = "name;state;city;sportOnly;rvpOnly")]
public JsonResult GetCenterByStateCityName(string name, string state, string city, bool sportOnly, bool rvpOnly)
{
var result = GetCenterServiceClient().GetCentersByLocation(name, city, state, sportOnly, rvpOnly).OrderBy(c => c.Name).ToList();
return Json(result);
}
The Duration value is 3600 seconds here. Sot the cache will be valid for 1 hour. You need to give the VaryByParam property values because you want different results for different parameters.

Related

How to map querystring to action method parameters in MVC?

I have a url http://localhost/Home/DomSomething?t=123&s=TX and i want to route this URL to the following action method
public class HomeController
{
public ActionResult DoSomething(int taxYear,string state)
{
// do something here
}
}
since the query string names does not match with action method's parameter name, request is not routing to the action method.
If i change the url (just for testing) to http://localhost/Home/DomSomething?taxYear=123&state=TX then its working. (But i dont have access to change the request.)
I know there is Route attribute i can apply on the action method and that can map t to taxYear and s to state.
However i am not finding the correct syntax of Route attribute for this mapping, Can someone please help?
Option 1
If Query String parameters are always t and s, then you can use Prefix. Note that it won't accept taxYear and state anymore.
http://localhost:10096/home/DoSomething?t=123&s=TX
public ActionResult DoSomething([Bind(Prefix = "t")] int taxYear,
[Bind(Prefix = "s")] string state)
{
// do something here
}
Option 2
If you want to accept both URLs, then declare all parameters, and manually check which parameter has value -
http://localhost:10096/home/DoSomething?t=123&s=TX
http://localhost:10096/home/DoSomething?taxYear=123&state=TX
public ActionResult DoSomething(
int? t = null, int? taxYear = null, string s = "", string state = "")
{
// do something here
}
Option 3
If you don't mind using third party package, you can use ActionParameterAlias. It accepts both URLs.
http://localhost:10096/home/DoSomething?t=123&s=TX
http://localhost:10096/home/DoSomething?taxYear=123&state=TX
[ParameterAlias("taxYear", "t")]
[ParameterAlias("state", "s")]
public ActionResult DoSomething(int taxYear, string state)
{
// do something here
}

OutputCache attribute and VaryByCustom without parameter

I'm trying to use the OutputCache attribute to cache pages depending on the language users selected.
[OutputCache(Duration = 86400, Location = OutputCacheLocation.Client, VaryByParam = "", VaryByCustom = "lang")]
public ActionResult MyActionMethod()
{
...
}
It works fine when we are on the page and we change the language, cool!
But the thing is: when a user calls the page for the first time, there is no "lang" parameter. So the cache will be created without parameter and it won't be replace if we change the language after.
How can I manage this case, when there is no parameter?
Any help would be appreciated, thanks!
You are talking about there is not "lang" parameter, you mean, there is no "lang" custom?
In global.asax you should have something like this:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if (custom == "lang")
{
string lang = null;
if (Request.UserLanguages != null && Request.UserLanguages.Length > 0)
{
lang = Request.UserLanguages.First().Split(new char[] { ';' }).First();
}
else
{
// Default
lang = "en-US";
}
return string.Format("lang={0}", lang.ToLower());
}
return base.GetVaryByCustomString(context, custom);
}
Then it will have the value "en-US" as default and otherwise get it from the browser in this case, or implement it using cookie.

Breeze WebAPI: How to combine QueryResult with ODataQueryOptions to return inlineCount

I tried different ways of loading a list of items using breeze and the API Controller, using filters (partly using a custom object and another part using ODataQueryOptions), but none of them turn out to be really successful.
Test code in javascript:
function test() {
EntityQuery
.from("Products")
/*
.withParameters({
filters: [
{ column: "Name", value: "12" }
]})
*/
.orderBy("Name desc")
.skip(30)
.take(15)
.inlineCount(true)
.using(manager)
.execute()
.then(success)
.fail(fail);
function success(data) {
console.log(data.products);
console.log(data.inlineCount);
}
function fail(error) {
console.log(error.message);
}
};
test();
Ideally I would like to accomplish this using something like this:
public IQueryable<Product> Products([FromUri] FilterObject[] filters, ODataQueryOptions odataQueryOptions)
{
var result = DummyData.GetProducts();
//var totalRowCount = result.Count();
return result;
}
The data will be filtered somewhere else (using nHibernate), I removed the part used to parse filters etc. However, this will never work since the other layer will return the total row count.
So I tried to replace it with :
public QueryResult Products([FromUri] FilterObject[] filters, ODataQueryOptions odataQueryOptions)
{
...
return new QueryResult
{
InlineCount = totalRowCount,
Results = result
};
}
This throws an error:
Cannot create an EDM model as the action 'Products' on controller 'Product' has a return type 'Breeze.WebApi.QueryResult' that does not implement IEnumerable.
The error disappears when removing the ODataQueryOptions variable. A search didn't give me valuable feedback.
I tried this:
public PageResult<Product> Products([FromUri] FilterObject[] filters, ODataQueryOptions odataQueryOptions)
{
....
return new PageResult<Product>(result, null, totalRowCount);
}
This won't throw an error. When opening the returned data object contains an inlineCount parameter with value being undefined, the actual data is in the first item in a nested results array (Count, Items and NextPageLink).
Is this the only way to get this working?
This can be reproduced in the NoDb sample of breeze, by adding ODataQueryOptions as parameter to the TodoLists method:
// GET ~/breeze/BreezeTodo/TodoList
[HttpGet]
public IQueryable<TodoList> TodoLists(ODataQueryOptions odataQueryOptions)
//public IQueryable<TodoList> TodoLists()
{
var result = _repository.TodoLists;
result = result.OrderByDescending(t => t.TodoListId);
return result;
}
Using
return breeze.EntityQuery
.from("TodoLists")
.inlineCount(true)
.skip(0).take(15)
.using(manager).execute()
.then(getSucceeded)
.fail(getFailed);
The request looks like this:
GET /breeze/Todo/TodoLists?$top=15&$inlinecount=allpages HTTP/1.1
The fiddle result with the ODataQueryOptions:
[{"$id":"1","$type":"NoDb.Models.TodoList, NoDb","TodoListId":1,"Title":"Before work","Todos":[{"$id":"2","$type":"NoDb.Models.TodoItem, NoDb","TodoItemId":1,"Title":"Make coffee","IsDone":false,"TodoListId":1,"TodoList":{"$ref":"1"}},{"$id":"3","$type":"NoDb.Models.TodoItem, NoDb","TodoItemId":2,"Title":"Turn heater off","IsDone":false,"TodoListId":1,"TodoList":{"$ref":"1"}}]}]
And without:
{"$id":"1","$type":"Breeze.WebApi.QueryResult, Breeze.WebApi","Results":[{"$id":"2","$type":"NoDb.Models.TodoList, NoDb","TodoListId":1,"Title":"Before work","Todos":[{"$id":"3","$type":"NoDb.Models.TodoItem, NoDb","TodoItemId":1,"Title":"Make coffee","IsDone":false,"TodoListId":1,"TodoList":{"$ref":"2"}},{"$id":"4","$type":"NoDb.Models.TodoItem, NoDb","TodoItemId":2,"Title":"Turn heater off","IsDone":false,"TodoListId":1,"TodoList":{"$ref":"2"}}]}],"InlineCount":1}
The solution appears to be:
public QueryResult TodoLists(ODataQueryOptions<TodoList> odataQueryOptions)
Fiddler:
Request:
GET /breeze/Todo/TodoLists?$top=15&$inlinecount=allpages HTTP/1.1
Response:
{"$id":"1","$type":"Breeze.WebApi.QueryResult, Breeze.WebApi","Results":[{"$id":"2","$type":"NoDb.Models.TodoList, NoDb","TodoListId":1,"Title":"Before work","Todos":[{"$id":"3","$type":"NoDb.Models.TodoItem, NoDb","TodoItemId":1,"Title":"Make coffee","IsDone":false,"TodoListId":1,"TodoList":{"$ref":"2"}},{"$id":"4","$type":"NoDb.Models.TodoItem, NoDb","TodoItemId":2,"Title":"Turn heater off","IsDone":false,"TodoListId":1,"TodoList":{"$ref":"2"}}]}],"InlineCount":1}

Choose OutputCache policy based on query string

I have an ASP.NET MVC application where certain resources are addressed like this:
/controller/action/id?revision=123
The revision parameter is optional:
if it is missing I do a 302 redirect to the latest revision. I want this redirection response to be cached only for a short while, or not at all.
if it is present, I want to cache the response for a long time because any given revision of the resource is immutable.
My first attempt was to do something like this:
[OutputCache(Duration=10,Location=OutputCacheLocation.Server)]
public Action(string id)
{
long lastRevision = GetLastRevision(id);
return RedirectToAction("Action",
new { Id = id, revision = lastRevision });
}
[OutputCache(Duration=int.MaxValue,Location=OutputCacheLocation.Server)]
public Action(string id, long revision)
{
// ...
}
Unfortunately, the ASP.NET MVC routing doesn't seem to like method overloads. It expects to have a single Action method with an optional parameter instead (i.e. long? revision), but then I can't specify different caching policies for both cases.
How can I chose a different caching policy based on the presence of the query string here?
You could write a custom method selector:
public class RevisionMethodSelectorAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
var revision = controllerContext.Controller.ValueProvider.GetValue("revision");
var hasRevisionParam = methodInfo.GetParameters().Any(p => string.Equals("revision", p.Name, StringComparison.OrdinalIgnoreCase));
if (revision != null && !string.IsNullOrEmpty(revision.AttemptedValue) && hasRevisionParam)
{
return true;
}
if ((revision == null || string.IsNullOrEmpty(revision.AttemptedValue)) && !hasRevisionParam)
{
return true;
}
return false;
}
}
and then decorate the 2 actions with it:
[RevisionMethodSelector]
public ActionResult MyAction(string id)
{
long lastRevision = GetLastRevision(id);
return RedirectToAction("MyAction", new { id = id, revision = lastRevision });
}
[RevisionMethodSelector]
[OutputCache(Duration = int.MaxValue, Location = OutputCacheLocation.Server, VaryByParam = "revision")]
public ActionResult MyAction(string id, long revision)
{
...
}
The first action is not cached. It will be picked up if there's no revision parameter in the request and it will simply redirect to the second action. The second action is cached for a very long time, this cache is made to vary according to the revision parameter value (which you didn't have) and will be picked by the custom method selector if a revision parameter is present in the request.
It turns out that I had already solved this problem without realizing it by making use of 302 redirects: apparently 302 responses are not cached even if you have an OutputCache attribute on your controller method!
Therefore both cases can be handled by a single controller method with the [OutputCache(...)] attribute specifying what to do for 200 responses.
Though this now begs the question of what to do if you do want to cache a 302...

How can I pass a list of objects to an ASP.NET MVC action using jQuery?

I have defined an object type in .NET that I want receive in a List<> as the input to an ASP.NET MVC action method?
Here is the action method and class I'm trying to receive.
public class WhereClause
{
public string ColumnInformation { get; set; }
public string WhereValue { get; set; }
public string AndOr { get; set; }
public string Comparer { get; set; }
}
public ActionResult Grid(string query, int skip = 0, int take = 50, List<WhereClause> whereClauses = null)
{
GridViewModel gvm = new GridViewModel();
gvm.Query = query;
And here is the Javascript where I'm building up the collection from a set of table rows using jQuery and then calling the jQuery ajax() method.
var whereClauses = [];
// Iterate over every row in the table and pull the values fromthe cells.
divQueryWidget.find('.tblWhereClauses tr').each(function (x, y) {
var tds = $(y).find('td');
var columnInformation = $(tds[0]).html();
var whereValue = $(tds[1]).html();
var andOr = $(tds[2]).html();
var comparer = $(tds[4]).html();
// Create a whereClause object
var whereClause = {};
whereClause.ColumnInformation = columnInformation;
whereClause.WhereValue = whereValue;
whereClause.AndOr = andOr;
whereClause.Comparer = comparer;
whereClauses.push({
ColumnInformation: columnInformation,
WhereValue: whereValue,
AndOr: andOr,
Comparer: comparer
});
});
//divQueryWidget.find('#queryResultsGrid').
$.ajax({
type: 'GET',
url: '<%= Url.Action("Grid", "Query") %>',
dataType: 'html',
data: { query: divQueryWidget.find('#activeQuery').val(), whereClauses: whereClauses },
success: function (data, textStatus, XMLHttpRequest) { divQueryWidget.find('#queryResultsGrid').append(data); divQueryWidget.find('.loading').css('visibility', 'hidden'); }
});
Here is where things get interesting. When the javascript is called and there were two rows in the table that are supposed to be passed to the MVC action, notice how when I debug into the code that there were two objects created in the list but their properties weren't filled.
What am I doing wrong that is preventing my Javascript object from being converted into a .NET List<> type? Should I be using array? Do I need to mark something as serializable?
i'd be interested in the results. i never tried to post such big chuncks of data with jQuery Ajax, but i guess it should be possible.
i think the problem here is about the labels.
when you make a List<> of items, in a normal view, with a foreach loop for example, the labels of the values have keys. you are missing those keys, and i think thats why it doesnt work.
for example, i have a List which i make with jQuery, but send in a normal postback.
in the FormCollection object i get the following keys
[0] "Vrbl_Titel" string
[1] "Sch_ID" string
[2] "Vragen[0].Evvr_Vraag" string
[3] "Vragen[0].Evvr_Type" string
[4] "Vragen[1].Evvr_Vraag" string
[5] "Vragen[1].Evvr_Type" string
[6] "Vragen[2].Evvr_Vraag" string
[7] "Vragen[2].Evvr_Type" string
a Vragen object has 2 strings, as you can see, so this is how it looks, and i guess this is how you have to make it in jQuery, before posting it to the server.
be careful though, the integer between the brackets should be without interruption. if you have an interruption (for example, 0 1 2 4 5 6) then MVC will stop at 2.
It might have something to do with the name of the fields of the objects being enclosed in parentheses when sent by Jquery (you can confirm this in Firebug).

Resources