Whats the recommended way to cache 'expensive' queries in an MVC HTML Helper? - model-view-controller

I am trying to use output caching on an HTML Helper. However, even with the attribute set, this code block is always entered when the Helper method is called. Since the outputcache attribute won't work in this scenario, what would be the recommended way of caching "expensive" queries in Html Helpers?
[OutputCache(Duration = 60)]
public static MvcHtmlString CountryDropDownListFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object selectedValue)
{
var doc = new XmlDocument();
doc.Load(HttpContext.Current.Server.MapPath("~/App_Data/countries.xml"));
var items = new Dictionary<string, string>();
foreach (XmlNode node in doc.SelectNodes("//country"))
{
items.Add(node.InnerText, node.InnerText);
}
return html.DropDownListFor(expression, new SelectList(items, "key", "value", selectedValue));
}

Output caching lets you store the output of an action method in memory on the Web server. For example, if the action method renders a view, the view page will be cached. This cached page is then available to the application for subsequent requests. Output caching saves your application the time and resources it would take to re-create the result of the action method.
In ASP.NET MVC, you can use the OutputCacheAttribute attribute to mark action methods whose output you want to cache. If you mark a controller with the OutputCacheAttribute attribute, the output of all action methods in the controller will be cached.
details http://msdn.microsoft.com/en-us/library/system.web.mvc.outputcacheattribute(v=vs.108).aspx
You use this attribure for not action method
Correct Example
[OutputCache(Duration = 50000)]
public ActionResult CountryDropDownListFor()
{
// Code
}
And in your view you can use Html.PartialAction to render one

Related

turn off data-val-* attribute on primitive types

Does anybody know a way to turn off MVC3 automatically decorating primitive types with a data-val-* attribute.
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
removes the data-val-required attribute, but I can't seem to find a way to turn off primitive types eg: data-val-number
I have a lot of hidden int fields which don't required validating on a form, but because of these attributes they are getting validated, causing my app to appear frozen.
I imagine that the hidden int fields have the [Required] data annotations defined on them in the viewmodel? If so then I believe you just need to remove the data annotation to prevent the data-val-required attribute from being displayed.
I could be wrong, but I suspect you will then say that the field is required when that viewmodel is used in some other views?
If this is the case, then rather than turning off the data annotations (which is essentially a work around) then you need to define your view models correctly. Ideally, each view model should be specific for the view that it is defined (see pattern 3 of the following link). This will avoid the issues where you have fields that are required on some views and are not required on others.
I couldn't seem to find a way to turn this off, so created my own HtmlHelper as a way to get around this issue.
public static IHtmlString HiddenInputFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
var memberExpression = (MemberExpression)expression.Body;
string fullID = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(memberExpression.Member.Name);
var builder = new TagBuilder("input");
builder.MergeAttribute("type", "hidden");
var value = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
builder.MergeAttribute("value", value.ToString());
string fullName = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(expression));
builder.MergeAttribute("name", fullName);
builder.GenerateId(fullID);
var tag = builder.ToString(TagRenderMode.SelfClosing);
return new HtmlString(tag);
}
I've noticed that if you load a partial view from an ajax request, the validations (data-val-*) inside the partial view are not automatically added. So I finally changed my code to load from ajax the heavy form data that doesn't need validations.
You can specify data-val="false" in the HTML input which you are creating on the page, for example:
<input type="checkbox" name="foo" value="#item.foo" class="input-validation-error"
data-val="false">

the best way to maintain html snippets in ASP.NET MVC to return in ajax calls

i'm looking for a best practices type answer here. basically i have a very chatty application which will be returning bits of data to the client very often. the bits of data returned eventually will end up being html added dynamically to the dom. so i'm trying to choose between the following 2 ways:
return just json data, create the html on the client side using jquery and possibly jquery templates
return the actual html that is build on the server side
i would like to make the choice that is most easily maintained. that is, i want the best way that will allow me to make updates to the html snippets very often.
i'm actually looking for a way to do #2 using ASP MVC partial views and want the ability to use string formatting. essentially i'm looking to make a call like this:
string sHtml = string.Format(GetNewTradeHtml(), "GOOG", "100", "635.50");
and I want GetNewTradeHtml() to actually get the html from a ASP MVC view instead of a string constant that might look like:
const string cNewTradeHtml = "<li><span>Symbol: {0}</span><span>Qty: {1}</span><span>Price: {2}</span></li>";
the string constants seems to be a popular way to do these kinds of things and i hate maintaining those...
basically i think i'm looking for a way to manage view several view templates that i can call ToString() on and get the raw html and use string formatting on it. and i'm hoping there is a suggested way to solve my particular problem natively in ASP MVC (without some hack). but perhaps (unfortunately) the string constants + string.format is the best way to maintain server side dynamic html...
UPDATE:
here's what i've learned since i've posted this question:
there are LOTS of posts here on SO about rendering a view into a string. a lot of different ways, some work with different versions of MVC some don't. some are cleaner than others, some are pretty heavy... ALL of which are normally some type of solution that require a controller context. so in most cases the solutions work great as responses to requests. but for my case, i need to do it outside of the context of a controller so now i need to either mock the controller or make a bunch of fake objects, neither of which i really want to deal with.
so i've determined that there is actually NO easy way to render a razor partial into its string representation without using a controller in a response. they really need to make an easy way to do this without mocking up controller context and request objects.
What are Views in asp.net mvc? They are just html templates, nothing more. They take model and replace template placeholders with model values. And indeed there's no more natural way to render html in asp.net mvc than using Views.
First, declare your view model
public class NewTradeViewModel
{
public string Symbol { get; set; }
public decimal Quantity { get; set; }
public decimal Price { get; set; }
}
than your controller action
public ViewResult GetNewTrade()
{
NewTradeViewModel model = new NewTradeViewModel;
model.Symbol = "GOOG";
model.Quantity = "100";
model.Price = 635.50m;
// PartialView, as you want just html snippets, not full layouts with master pages, etc
return PartialView("TemplateViewName", model);
}
and the very ordinary view - you may have any number of these, just change controller action to return specific one
#model NewTradeViewModel
<li><span>Symbol: #Model.Symbol</span><span>Qty: #Model.Quantity</span><span>Price: #Model.Price</span></li>
Since you mentioned that your app was "chatty" you should probably consider returning Json and rendering on the client side with a template engine.
This is really a toss up though because it looks like your snippets are pretty small.
If you do go with a sending JSON back and forth, I can recommend jquery templates or mustache
backbone.js can also help you better organize your client side components. It is pretty easy to get up and running with it. By default it works with jquery templates, but you can also plug in other templates if you like.
Here is a simple approach to storing templates in separate files, http://encosia.com/using-external-templates-with-jquery-templates/
ijjo,
Just looked at your question again and notice that you are referring to returning the html partialview as a string. there are loads of references here on SO to this type od function, but below is my version taken from an 'old' mvc app that's still in production. without further ado, it's an extension method that hooks into the controller:
public static class ExtensionMethods
{
public static string RenderPartialToString(this ControllerBase controller, string partialName, object model)
{
var vd = new ViewDataDictionary(controller.ViewData);
var vp = new ViewPage
{
ViewData = vd,
ViewContext = new ViewContext(),
Url = new UrlHelper(controller.ControllerContext.RequestContext)
};
ViewEngineResult result = ViewEngines
.Engines
.FindPartialView(controller.ControllerContext, partialName);
if (result.View == null)
{
throw new InvalidOperationException(
string.Format("The partial view '{0}' could not be found", partialName));
}
var partialPath = ((WebFormView)result.View).ViewPath;
vp.ViewData.Model = model;
Control control = vp.LoadControl(partialPath);
vp.Controls.Add(control);
var sb = new StringBuilder();
using (var sw = new StringWriter(sb))
{
using (var tw = new HtmlTextWriter(sw))
{
vp.RenderControl(tw);
}
}
return sb.ToString();
}
}
usage (as per archil's example above):
public ViewResult GetNewTrade()
{
NewTradeViewModel model = new NewTradeViewModel;
model.Symbol = "GOOG";
model.Quantity = "100";
model.Price = 635.50m;
// PartialView, as you want just html snippets, not full layouts with master pages, etc
return this.RenderPartialToString("TemplateViewName", model);
}
good luck and enjoy...

How do I pass an object from the Index view to the edit view using MVC3

I have created a simple WCF service that is to be configured by an MVC3 UI.
When I call the index page from my controller, I want to display the values held in the configuration, which has been returned by the service. The user could then chose to edit these settings and then send them back to the service.
I want to do something like this in the index view ...
<div>
#Html.ActionLink("Edit", "Edit", model)
</div>
and then consume the model in the controller like this...
[HttpPost]
public ActionResult Edit( SettingsModel Config)
{
try
{
List<string> configErrors = null;
if (ModelState.IsValid)
{
// Set up a channel factory to use the webHTTPBinding
using (WebChannelFactory<IChangeService> serviceChannel = new WebChannelFactory<IChangeService>(new Uri(baseServiceUrl)))
{
IChangeService channel = serviceChannel.CreateChannel();
configErrors = channel.SetSysConfig(Config);
}
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
but this doesn't work.
Any suggestions???
When the form gets posted, all the input type fields data is collected and sent to the server. You can see this data using FireBug. The key point here is that, is the data that is being posted in a form, that MVC's default model binder can understand and map it to the model object, which is being passed as input parameter to the action method.
In your case, the model is of type "SettingsModel". You have to ensure that, the form data that is being posted is in format, that can be mapped to the "SettingsModel" object.
Same kind of question discussed in another thread : Can't figure out why model is null on postback?
Check Out this article : NerdDinner Step 6: ViewData and ViewModel
In the above article, carefully go through the "Using a ViewModel Pattern" section. My guess is that, this is what you are looking for.
You will need to post the values to populate the SettingsModel object on the Edit action. You can do this using hidden form fields if you don't want the user to see it. Otherwise you could have no parameters on the Edit action and make another call to the web service to populate the Settings model.

MVC 3 - Html.EditorFor seems to cache old values after $.ajax call

This is a follow on from the following question:
MVC 3 + $.ajax - response seems to be caching output from partial view
There is a detailed description of the problem over there. However, I have now managed to narrow down the problem, that seems to be with the Html.EditorFor helpers, hence the new question.
The issue:
I post data to the server using $.ajax, then return the html of the partial view that holds the input controls. The problem is that, despite passing a newly created object to the Partial Views model, the various #Html.EditorFor and #Html.DropDownListFor helpers return the OLD DATA!.
I can prove that the model has correctly passed in a new object to the helpers, by printing the value out beside the Html helper. Ie:
#Html.EditorFor(model => model.Transaction.TransactionDate)
#Model.Transaction.TransactionDate.ToString()
As the following image shows, the #Html.EditorFor is returning the wrong data:
[Note that the value beside the Comentario text box is a date time, because I was testing replacing the default values with a value that would change with each post, ie, a DateTime.]
If I replace the #Html.EditorFor for TransactionDate with a plain old #Html.TextBox():
#Html.TextBox("Transaction_TransactionDate", Model.Transaction.TransactionDate)
Then it renders the correct TransactionDate value for a new Transaction object, ie, DateTime.MinValue (01/01/0001...).
Therefore...
The problem is with the #Html.EditorFor helpers. The problem also happens with TextBoxFor and DropDownListFor.
The problem being that these helpers seem to cache the old value.
What am I doing wrong??!
EDIT:
I have just tried debugging in the custom Editor template for dates, and in there, ViewData.TemplateInfo.FormattedModelValue shows the correct value, ie, "01/01/0001". However, once it gets to Fiddler, the response is showing the old date, eg, "01/09/2011" in the image above.
As a result, I just think that there is some caching going on here, but I have none set up, so nothing makes any sense.
There is no caching involved here. It's just how HTML helper work. They first look at the ModelState when binding their values and then in the model. So if you intend to modify any of the POSTed values inside your controller action make sure you remove them from the model state first:
[HttpPost]
public virtual ActionResult AjaxCreate(Transaction transaction)
{
if (ModelState.IsValid)
{
service.InsertOrUpdate(transaction);
service.Save();
}
service.ChosenCostCentreId = transaction.IdCostCentre;
TransactionViewModel viewModel = new TransactionViewModel();
ModelState.Remove("Transaction");
viewModel.Transaction = new Transaction();
ModelState.Remove("CostCentre");
viewModel.CostCentre = service.ChosenCostCentre;
...
return PartialView("_Create", viewModel);
}
Even if you do not specify caching, it sometimes can occur. For my controllers which handle AJAX and JSON requests, I decorate them as follows:
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
This specifically declares no caching should occur.
UPDATE
Based on an answer Darin Dimitrov gave here, try adding the following line to your controller action:
ModelState.Clear();
i have never seen this but basically if you are using ajax to request this data, you need to set nochache: i am assuming you using jQuery.ajax here so will show the code:
$.ajax({
url: "somecontroller/someAction,
cache: false, // this is key to make sure JQUERY does not cache your request
success: function( data ) {
alert( data );
}
});
just a stab in the dark, i assume you have probably already covered this already. have you tried to create a new model first and then populate that new instance of the model with your data, and then send this to your view!
Finally not sure what DB server your using but have you check to see that DB results are not cached and that you are not just requesting SQL results from the DB cache... i dont use MsSQL but i hear that it has outputCaching until something is change on the DB server itself?? anyway just a few thoughts
This was unexpected behavior for me, and although I understand the reason why it's necessary to give ModelState precedence, I needed a way to remove that entry so that the value from Model is used instead.
Here are a couple methods I came up with to assist with this. The RemoveStateFor method will take a ModelStateDictionary, a Model, and an expression for the desired property, and remove it.
HiddenForModel can be used in your View to create a hidden input field using only the value from the Model, by first removing its ModelState entry. (This could easily be expanded for the other helper extension methods).
/// <summary>
/// Returns a hidden input field for the specified property. The corresponding value will first be removed from
/// the ModelState to ensure that the current Model value is shown.
/// </summary>
public static MvcHtmlString HiddenForModel<TModel, TProperty>(this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression)
{
RemoveStateFor(helper.ViewData.ModelState, helper.ViewData.Model, expression);
return helper.HiddenFor(expression);
}
/// <summary>
/// Removes the ModelState entry corresponding to the specified property on the model. Call this when changing
/// Model values on the server after a postback, to prevent ModelState entries from taking precedence.
/// </summary>
public static void RemoveStateFor<TModel, TProperty>(this ModelStateDictionary modelState, TModel model,
Expression<Func<TModel, TProperty>> expression)
{
var key = ExpressionHelper.GetExpressionText(expression);
modelState.Remove(key);
}
Call from a controller like this:
ModelState.RemoveStateFor(model, m => m.MySubProperty.MySubValue);
or from a view like this:
#Html.HiddenForModel(m => m.MySubProperty.MySubValue)
It uses System.Web.Mvc.ExpressionHelper to get the name of the ModelState property. This is especially useful when you have "Nested" models since the key name isn't obvious.
Make sure you're not doing this:
#Html.EditorFor(model => model.Transaction.TransactionDate.Date)
I did this, and the model never got the value back. It worked perfectly once I remove the .Date.

How to set a hidden value in Razor

I know that what I'm trying to do is bad idea, but I have specific constrains for now.
I have multiple sites, using one and the same MVC3 code base. For one of them the requirement is to hide some required fields from the form.
I know that the best approach is to modify the controller to set the defaults for these fields, but I'd like to achieve this modifying only the view for this particular site w/o changing the code.
So, how do I set a particular model property to a default value in the view? The ideal should be something like:
#Html.HiddenFor(model => model.RequiredProperty)
#model.RequiredProperty = "default"
EDIT: more explanation
So, actually this is in a sub-view, which is used by 2 different main views. I need these properties set only if one particular main view is used, and not the others.
So, I guess the setting to default need to go to that particular "main" view. Looks like I can not use HiddenFor in the sub-view, and then Html.Hidden in the main.
Is there a way to check in the sub-view which is the outer view?
If I understand correct you will have something like this:
<input value="default" id="sth" name="sth" type="hidden">
And to get it you have to write:
#Html.HiddenFor(m => m.sth, new { Value = "default" })
for Strongly-typed view.
There is a Hidden helper alongside HiddenFor which lets you set the value.
#Html.Hidden("RequiredProperty", "default")
EDIT Based on the edit you've made to the question, you could do this, but I believe you're moving into territory where it will be cheaper and more effective, in the long run, to fight for making the code change. As has been said, even by yourself, the controller or view model should be setting the default.
This code:
<ul>
#{
var stacks = new System.Diagnostics.StackTrace().GetFrames();
foreach (var frame in stacks)
{
<li>#frame.GetMethod().Name - #frame.GetMethod().DeclaringType</li>
}
}
</ul>
Will give output like this:
Execute - ASP._Page_Views_ViewDirectoryX__SubView_cshtml
ExecutePageHierarchy - System.Web.WebPages.WebPageBase
ExecutePageHierarchy - System.Web.Mvc.WebViewPage
ExecutePageHierarchy - System.Web.WebPages.WebPageBase
RenderView - System.Web.Mvc.RazorView
Render - System.Web.Mvc.BuildManagerCompiledView
RenderPartialInternal - System.Web.Mvc.HtmlHelper
RenderPartial - System.Web.Mvc.Html.RenderPartialExtensions
Execute - ASP._Page_Views_ViewDirectoryY__MainView_cshtml
So assuming the MVC framework will always go through the same stack, you can grab var frame = stacks[8]; and use the declaring type to determine who your parent view is, and then use that determination to set (or not) the default value. You could also walk the stack instead of directly grabbing [8] which would be safer but even less efficient.
While I would have gone with Piotr's answer (because it's all in one line), I was surprised that your sample is closer to your solution than you think. From what you have, you simply assign the model value before you use the Html helper method.
#{Model.RequiredProperty = "default";}
#Html.HiddenFor(model => model.RequiredProperty)
How about like this
public static MvcHtmlString HiddenFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object value, object htmlAttributes)
{
return HiddenFor(htmlHelper, expression, value, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
public static MvcHtmlString HiddenFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object value, IDictionary<string, object> htmlAttributes)
{
return htmlHelper.Hidden(ExpressionHelper.GetExpressionText(expression), value, htmlAttributes);
}
Use it like this
#Html.HiddenFor(customerId => reviewModel.CustomerId, Site.LoggedInCustomerId, null)

Resources