Cache based on Url Parameter in Asp.net MVC - caching

i have defined a route culture/Controller/action/id... my controller contains following action..
[OutputCache(Duration=60*10)]
public ActionResult Index()
{*/do magic here/*}
is it possible to Cache contents based on Culture?

The localization complete guide presents an example of how to achieve this using the VaryByCustom parameter. In global.asax you would override the GetVaryByCustomString method:
public override string GetVaryByCustomString(HttpContext context, string value)
{
if (value == "lang")
{
return Thread.CurrentThread.CurrentUICulture.Name;
}
return base.GetVaryByCustomString(context, value);
}
and then:
[OutputCache(Duration = 60 * 10, VaryByParam = "none", VaryByCustom = "lang")]
public ActionResult Index()
{
/* do magic here */
...
}
Or if you want to rely solely on the culture route data parameter you could do this:
public override string GetVaryByCustomString(HttpContext context, string value)
{
if (value == "lang")
{
var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(context));
var culture = (string)routeData.Values["culture"];
if (!string.IsNullOrEmpty(culture))
{
return culture;
}
}
return base.GetVaryByCustomString(context, value);
}

Related

JQuery custom validation attribute MVC core

I try to add a custom attribute to validate required field and trim value for white space.
So here is my custom attribute :
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class CustomRequired : ValidationAttribute, IClientModelValidator
{
public CustomRequired()
{
ErrorMessage = new ResourceManager(typeof(ErrorResource)).GetString("All_Required");
}
public void AddValidation(ClientModelValidationContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-customrequired", ErrorMessage);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
return value.ToString().Trim().Length > 0 ? ValidationResult.Success : new ValidationResult(ErrorMessage);
}
private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
And here how I add it (or try) :
$(document).ready(function () {
$.validator.addMethod("customrequired", function (value, element, parameters) {
return $.trim(value).length > 0;
});
$.validator.unobtrusive.adapters.addBool('customrequired');
});
And set it on property in a viewmodel :
[CustomRequired]
public string Code { get; set; }
My problem is it doesn't had any client side validation whereas the function is in the jQuery validator... The ModelState is invalid so the controller reject it but I want a client side validation.
console:
Edit :
I forgot to say I'm using kendo... See my own answer below.
I forgot to say that I'm using kendo...
My code is functional with a classic validation but not with kendo edit pop-up. :/
So here is the solution for those who have the same problem, write this in your javascript instead of add it in the $.validator :
(function ($, kendo) {
$.extend(true, kendo.ui.validator, {
rules: {
customrequired: function (input) {
if (input.is("[data-val-customrequired]")) {
return $.trim(input.val()).length > 0;
}
return true;
}
},
messages: {
customrequired: function (input) {
return input.attr("data-val-customrequired");
}
}
});
})(jQuery, kendo);

Why is the masterPath parameter in CreateView empty?

Writing a small proof of concept application and wondering why the masterPath parameter is empty:
in application_start:
ViewEngines.Engines.Add(new AlternateLocationViewEngine(
new string[] {
"~/Views/Shared/_Layout.cshtml", //Is this correct? Can/should i do that
"~/Views/Shared/{0}.cshtml",
"~/Plugins/Views/Shared/{0}.cshtml",
},
new string[] {
"~/Plugins/Views/{1}/{0}.cshtml",
"~/Plugins/{1}/{0}.chstml",
"~/Plugins/Views/Shared/{0}.cshtml"
}
));
public class AlternateLocationViewEngine : RazorViewEngine
{
public AlternateLocationViewEngine(string[] masterLocations, string[] viewLocations)
: base()
{
MasterLocationFormats = masterLocations;
ViewLocationFormats = viewLocations;
PartialViewLocationFormats = ViewLocationFormats;
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
if (string.IsNullOrEmpty(masterPath))
{
masterPath = MasterLocationFormats.ElementAt(0);
}
var nameSpace = controllerContext.Controller.GetType().Namespace;
return base.CreateView(controllerContext, viewPath.Replace("%1", nameSpace), masterPath.Replace("%1", nameSpace));
}
}
As you see i 'm forced to check if masterPath is empty in method CreateView(). Why is this? Am i missing something fundamental?
My dev environment: ASP.NET MVC3, Razor, .NET4
The masterPath will only have a value when creating a ViewResult with a masterName.
protected internal ViewResult View(string viewName, string masterName);
Internally, the RazorView handles null masterPaths in it's constructor.
// where layoutPath is the masterPath arg from the RazorViewEngine's CreateView
LayoutPath = layoutPath ?? String.Empty;
When rendering the view, the RazorView will set the OverridenLayoutPath to the masterPath (if supplied).
// An overriden master layout might have been specified when the ViewActionResult got returned.
// We need to hold on to it so that we can set it on the inner page once it has executed.
webViewPage.OverridenLayoutPath = LayoutPath;
You do not need to specify the _Layout as one of the MasterLocationFormats. Below is the default behavior for the RazorViewEngine.
MasterLocationFormats = new[] {
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
};
You can checkout the source code for more inspiration.

ASP.NET MVC3 Physical Location of View from controller

What is the proper way to get the physical location of the View that will be served by a MVC action from inside the action?
I need the last modified time of the file for sending response headers.
The proper way to the get physical location of a view is to map its virtual path. The virtual path can be retrieved from the ViewPath property of BuildManagerCompiledView (RazorView derive from that class, and your IView instances will therefore typically have that property).
Here is an extension method that you can use:
public static class PhysicalViewPathExtension
{
public static string GetPhysicalViewPath(this ControllerBase controller, string viewName = null)
{
if (controller == null)
{
throw new ArgumentNullException("controller");
}
ControllerContext context = controller.ControllerContext;
if (string.IsNullOrEmpty(viewName))
{
viewName = context.RouteData.GetRequiredString("action");
}
var result = ViewEngines.Engines.FindView(context, viewName, null);
BuildManagerCompiledView compiledView = result.View as BuildManagerCompiledView;
if (compiledView != null)
{
string virtualPath = compiledView.ViewPath;
return context.HttpContext.Server.MapPath(virtualPath);
}
else
{
return null;
}
}
}
Use it something like this:
public ActionResult Index()
{
string physicalPath = this.GetPhysicalViewPath();
ViewData["PhysicalPath"] = physicalPath;
return View();
}
or:
public ActionResult MyAction()
{
string physicalPath = this.GetPhysicalViewPath("MyView");
ViewData["PhysicalPath"] = physicalPath;
return View("MyView");
}
That could work:
private DateTime? GetDate(string controller, string viewName)
{
var context = new ControllerContext(Request.RequestContext, this);
context.RouteData.Values["controller"] = controller;
var view = ViewEngines.Engines.FindView(context, viewName, null).View as BuildManagerCompiledView;
var path = view == null ? null : view.ViewPath;
return path == null ? (DateTime?) null : System.IO.File.GetLastWriteTime(path);
}

Creating Canonical URLs including an id and title slug

I want to replicate what StackOverflow does with its URLs.
For example:
Hidden Features of C#? - (Hidden Features of C#?)
or
Hidden Features of C#? - (Hidden Features of C#?)
Will Take you to the same page but when they return to the browser the first one is always returned.
How do you implement the change so the larger URL is returned?
The way that I've handled this before is to have two routes, registered in this order
routes.MapRoute(
null,
"questions/{id}/{title}",
new { controller = "Questions", action = "Index" },
new { id = #"\d+", title = #"[\w\-]*" });
routes.MapRoute(
null,
"questions/{id}",
new { controller = "Questions", action = "Index" },
new { id = #"\d+" });
now in the controller action,
public class QuestionsController
{
private readonly IQuestionRepository _questionRepo;
public QuestionsController(IQuestionRepository questionRepo)
{
_questionRepo = questionRepo;
}
public ActionResult Index(int id, string title)
{
var question = _questionRepo.Get(id);
if (string.IsNullOrWhiteSpace(title) || title != question.Title.ToSlug())
{
return RedirectToAction("Index", new { id, title = question.Title.ToSlug() }).AsMovedPermanently();
}
return View(question);
}
}
We'll permanently redirect to the URL that contains the title slug (lowercase title with hyphens as separators) if we only have the id. We also make sure that the title passed is the correct one by checking it against the slugged version of the question title, thereby creating a canonical URL for the question that contains both the id and the correct title slug.
A couple of the helpers used
public static class PermanentRedirectionExtensions
{
public static PermanentRedirectToRouteResult AsMovedPermanently
(this RedirectToRouteResult redirection)
{
return new PermanentRedirectToRouteResult(redirection);
}
}
public class PermanentRedirectToRouteResult : ActionResult
{
public RedirectToRouteResult Redirection { get; private set; }
public PermanentRedirectToRouteResult(RedirectToRouteResult redirection)
{
this.Redirection = redirection;
}
public override void ExecuteResult(ControllerContext context)
{
// After setting up a normal redirection, switch it to a 301
Redirection.ExecuteResult(context);
context.HttpContext.Response.StatusCode = 301;
context.HttpContext.Response.Status = "301 Moved Permanently";
}
}
public static class StringExtensions
{
private static readonly Encoding Encoding = Encoding.GetEncoding("Cyrillic");
public static string RemoveAccent(this string value)
{
byte[] bytes = Encoding.GetBytes(value);
return Encoding.ASCII.GetString(bytes);
}
public static string ToSlug(this string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return string.Empty;
}
var str = value.RemoveAccent().ToLowerInvariant();
str = Regex.Replace(str, #"[^a-z0-9\s-]", "");
str = Regex.Replace(str, #"\s+", " ").Trim();
str = str.Substring(0, str.Length <= 200 ? str.Length : 200).Trim();
str = Regex.Replace(str, #"\s", "-");
str = Regex.Replace(str, #"-+", "-");
return str;
}
}

ASP MVC 2 Validation : Passing Javascript code to the client

I am writing a custom validation attribute
It does conditional validation between two fields
When I create my rule, one of the things that I could not solve is how to pass javascript code through ValidationParameters
Usually, I just do
ValidationParameters["Param1"] = "{ required :function(element) { return $("#age").val() < 13;) }"
However, the MicrosoftMvcJQueryValidation.js routines trnasforms this to
Param1 = "{ required :function(element) { return $("#age").val() < 13;) }"
I could use Param1.eval() in Javascript. This will evaluates and executes the code but I just want to evalute the code and execute it later
JSON parser does not parse string contening Javascript code
So I am asking here for any idea
Not sure how you would inject javascript as you describe, but you may want to consider using the custom validation pattern for ASP.NET MVC 2.
Important pieces are the ValidationAttribute, DataAnnotationsModelValidator, registering the validator in Application_Start with DataAnnotationsModelValidatorProvider.RegisterAdapter, and the client side Sys.Mvc.ValidatorRegistry.validators function collection to register your client side validation code.
Here's the example code from my post.
[RegularExpression("[\\S]{6,}", ErrorMessage = "Must be at least 6 characters.")]
public string Password { get; set; }
[StringLength(128, ErrorMessage = "Must be under 128 characters.")]
[MinStringLength(3, ErrorMessage = "Must be at least 3 characters.")]
public string PasswordAnswer { get; set; }
public class MinStringLengthAttribute : ValidationAttribute
{
public int MinLength { get; set; }
public MinStringLengthAttribute(int minLength)
{
MinLength = minLength;
}
public override bool IsValid(object value)
{
if (null == value) return true; //not a required validator
var len = value.ToString().Length;
if (len < MinLength)
return false;
else
return true;
}
}
public class MinStringLengthValidator : DataAnnotationsModelValidator<MinStringLengthAttribute>
{
int minLength;
string message;
public MinStringLengthValidator(ModelMetadata metadata, ControllerContext context, MinStringLengthAttribute attribute)
: base(metadata, context, attribute)
{
minLength = attribute.MinLength;
message = attribute.ErrorMessage;
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var rule = new ModelClientValidationRule
{
ErrorMessage = message,
ValidationType = "minlen"
};
rule.ValidationParameters.Add("min", minLength);
return new[] { rule };
}
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MinStringLengthAttribute), typeof(MinStringLengthValidator));
}
Sys.Mvc.ValidatorRegistry.validators["minlen"] = function(rule) {
//initialization
var minLen = rule.ValidationParameters["min"];
//return validator function
return function(value, context) {
if (value.length < minLen)
return rule.ErrorMessage;
else
return true; /* success */
};
};

Resources