Mvc 3 outputcache varyByCustom extension - asp.net-mvc-3

[OutputCache(Duration = 120, VaryByCustom = "siteId")]
public ActionResult Index()
{
if(ConfigHelper.SiteId == 1)
//massive logic here;
else if(ConfigHelper.SiteId == 2)
//massive logic here;
else if(ConfigHelper.SiteId == 3)
//massive logic here;
return View(model);
}
// from Config helper
public static int SiteId
{
get { return GetAppSettingsValue<int>("SiteId", 0); }
}
Index method invoke when I type localhost. It is homepage.
Can anyone know how to get varyByCustom from config setting value ?

Related

How to set ASP.NET Web Api Route Constraint HTTP Status Return Code

I am using ASP.NET Web Api 2 framework and using a basic route constraint as below.
[Route("Number/{id:int:min(2):max(10)}")]
public HttpResponseMessage GetNumber([FromUri] int id)
{
return (id > 0)
? Request.CreateResponse(HttpStatusCode.OK, id)
: Request.CreateResponse(HttpStatusCode.PreconditionFailed);
}
I would like to know when the id is conflict with the constraint above, .e.g. 1 or 11,
how can I overrride the default HTTP Status Return code which is 404?
Thanks very much.
You can create a custom route constraint that will check the min, max values like you wish, and additionally allow you to specify the HttpStatusCode in case the constraint is not fulfilled correctly.
public class RangeWithStatusRouteConstraint : IHttpRouteConstraint
{
private readonly int _from;
private readonly int _to;
private readonly HttpStatusCode _statusCode;
public RangeWithStatusRouteConstraint(int from, int to, string statusCode)
{
_from = from;
_to = to;
if (!Enum.TryParse(statusCode, true, out _statusCode))
{
_statusCode = HttpStatusCode.NotFound;
}
}
public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values,
HttpRouteDirection routeDirection)
{
object value;
if (values.TryGetValue(parameterName, out value) && value != null)
{
var stringValue = value as string;
var intValue = 0;
if (stringValue != null && int.TryParse(stringValue, out intValue))
{
if (intValue >= _from && intValue <= _to)
{
return true;
}
//only throw if we had the expected type of value
//but it fell out of range
throw new HttpResponseException(_statusCode);
}
}
return false;
}
}
Now you need to register it in the attribute mapping:
var constraintResolver = new DefaultInlineConstraintResolver();
constraintResolver.ConstraintMap.Add("rangeWithStatus", typeof(RangeWithStatusRouteConstraint));
config.MapHttpAttributeRoutes(constraintResolver);
And your route can now look like this:
public class MyController : ApiController
{
[Route("Number/{id:int:rangeWithStatus(2, 10, PreconditionFailed)}")]
public HttpResponseMessage GetNumber([FromUri] int id)
{
return Request.CreateResponse(HttpStatusCode.OK, id);
}
}
in this case Conflict is a string representation of HttpStatusCode.Conflict enumeration, which is later cast to the enum value in the constraint.
With such setup, if the value falls out of the [2, 10] range, the Web API infrastructure will respond with 409 status code instead of the default 404.
Remove the constraint on the route, and do the validation inside the method.
Constraints are used to figure out if a route should be used at all, but you want to use the route to be hit but change the status code if the input falls outside some parameters.
[Route("Number/{id})] // No constraints
public HttpResponseMessage GetNumber([FromUri] int id)
{
return (id > 1 && id < 11) // here you validate id range
? Request.CreateResponse(HttpStatusCode.OK, id)
: Request.CreateResponse(HttpStatusCode.PreconditionFailed);
}
I think that the correct solution is to check constraint inside controller (as #aanund suggested). However if you want to keep the Route constraints and avoid conditionals in your code, you may create another action controller with the same route but without constraints. If the constraints are not verified the new action controller will be called:
[Route("Number/{id:int:min(2):max(10)}")]
public HttpResponseMessage GetNumber([FromUri] int id)
{
Request.CreateResponse(HttpStatusCode.OK, id)
}
[Route("Number/{id}")]
public HttpResponseMessage GetNumber2([FromUri] int id)
{
Request.CreateResponse(HttpStatusCode.PreconditionFailed);
}

MVC3 AuthorizeAttribute

This allows "frankl" to access but blocks the admins. What have I done wrong?
[Authorize(Order=1,Roles = "Admin",Users="frankl")]
public class AuthorizeBaseController_Admins_frank : Controller
{
}
It is probably simple but I don't see any examples that combine the two and the "Allowmultiple" property generates an error when I try to add it.
Thanks,
Chris
Roles and Users should be used exclusively. If you want to combine them you could write a custom authorize attribute:
public class MyAuthoirizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
var user = httpContext.User;
if (!user.Identity.IsAuthenticated)
{
return false;
}
var usersSplit = SplitString(Users);
var rolesSplit = SplitString(Roles);
return
(usersSplit.Length > 0 && usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) ||
(rolesSplit.Length > 0 && rolesSplit.Any(user.IsInRole));
}
private string[] SplitString(string original)
{
if (string.IsNullOrEmpty(original))
{
return new string[0];
}
return (from piece in original.Split(',')
let trimmed = piece.Trim()
where !string.IsNullOrEmpty(trimmed)
select trimmed).ToArray();
}
}
and then:
[MyAuthorize(Order = 1, Roles = "Admin", Users="frankl")]
public class AuthorizeBaseController_Admins_frank : Controller
{
...
}
Unfortunately the AuthorizeAttribrute will let you either specify valid users, or valid roles - not both. Here is the relevant bit of code from the MVC 3 source.
protected virtual bool AuthorizeCore(HttpContextBase httpContext) {
if (httpContext == null) {
throw new ArgumentNullException("httpContext");
}
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated) {
return false;
}
if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) {
return false;
}
if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole)) {
return false;
}
return true;
}
You will either need to make 'frankl' an Admin, or create a custom authorization attribrute

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);
}

Cast a dynamic attribute after it's posted (Model binding)

What I have is a model which has one of it's attributes dynamic. This dynamic attribute holds one of about 50 different objects. This model is send to a view that dynamic creates the page based on which object is used. This is working perfectly ... the issue is the postback. When the model posts back the modelbinder is not able to bind the dynamic attribute. I was expecting this and thought I would be able to handle it but nothing that I tried works appart from making an action for EACH different objects.
Model
public class VM_List
{
public Config.CIType CIType { get; set; }
public dynamic SearchData { get; set; }
//Lots of static fields
}
This works
public ActionResult List_Person(VM_List Model, VM_Person_List SearchData)
{
Model.SearchData = SearchData;
//Stuff
}
public ActionResult List_Car(VM_List Model, VM_Car_List SearchData)
{
Model.SearchData = SearchData;
//Stuff
}
But what I want is a single action
public ActionResult List(VM_List Model)
{
//Stuff
}
I have tried things like
public ActionResult List(VM_List Model)
{
switch (Model.CIType)
{
case Config.CIType.Person:
UpdateModel((VM_Person_List)Model.SearchData);
break;
default:
SearchData = null;
break;
}
//Stuff
}
and a Custom modelbinder
CIType CIType = (CIType)bindingContext.ValueProvider.GetValue("CIType").ConvertTo(typeof(CIType));
switch (CIType)
{
case Config.CIType.Person:
SearchData = (VM_Person_List)bindingContext.ValueProvider.GetValue("SearchData").ConvertTo(typeof(VM_Person_List));
break;
default:
SearchData = null;
break;
}
but I can't get either to work. Any ideas?
After trying many different things I finally found a way that works.
Action:
public ActionResult List(VM_List Model)
{
//If the defaultmodelbinder fails SearchData will be an object
if(Model.SearchData.GetType() == typeof(object))
{
//Get SearchData as a Dictionary
Dictionary<string, string> DSearchData = Request.QueryString.AllKeys.Where(k => k.StartsWith("SearchData.")).ToDictionary(k => k.Substring(11), k => Request.QueryString[k]);
switch (Model.CIType)
{
case Config.CIType.Person:
Model.SearchData = new VM_Person_List(DSearchData);
break;
case Config.CIType.Car:
Model.SearchData = new VM_Car_List(DSearchData);
break;
}
//Rest of action
//..
}
and for each object make a constructor that accepts a dictionary
public VM_Car_List(Dictionary<string, string> DSearchData)
{
this.Make = Convert.ToInt32(DSearchData["Make"]);
this.Model = Convert.ToInt32(DSearchData["Model"]);
this.Year = Convert.ToInt32(DSearchData["Year"]);
// ETC
}

Cache based on Url Parameter in Asp.net MVC

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);
}

Resources