I have a custom ValidationAttribute that implements IClientValidatable. But the GetClientValidationRules is never called to actually output the validation rules to the client side.
There is nothing special about the attribute but for some reason it is never called. I've tried registering an adapter in Application_Start() but that also doesnt work.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class CustomAttribute : ValidationAttribute, IClientValidatable
{
public override bool IsValid(object value)
{
return true;
}
#region IClientValidatable Members
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
string errorMessage = FormatErrorMessage(metadata.GetDisplayName());
yield return new ModelClientValidationRule { ErrorMessage = errorMessage, ValidationType = "custom" };
}
#endregion
}
public class CustomAdapter : DataAnnotationsModelValidator<CustomAttribute>
{
public CustomAdapter(ModelMetadata metadata, ControllerContext context, CustomAttribute attribute)
: base(metadata, context, attribute)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
return this.Attribute.GetClientValidationRules(this.Metadata, this.ControllerContext);
}
}
In Application_Start() I have:
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(CustomAttribute), typeof(CustomAdapter));
When I put a breakpoint inside GetClientValidationRules it is never hit.
In order GetClientValidationRules() method to get called you must enable client-side validation support. It can be done in the following ways:
In the web.config (for all pages of application):
<appSettings>
<add key="ClientValidationEnabled" value="true" />
Or on particular view only:
either
#{ Html.EnableClientValidation(); }
or
#(ViewContext.ClientValidationEnabled = true)
Please note it must go before
#using (Html.BeginForm())
statement.
If you are using jquery unobtrusive validation (which seems to be a standard currently), you'll also need to enable it:
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
in web.config or
#Html.EnableUnobtrusiveJavaScript()
for particular views.
Related
I have a controller action and I want to use changeable authorization via web config settings.
public class ProductsController : ApiController
{
[HttpGet, Authorize]
public Product FindProduct(id) {}
}
<appSettings>
<add key="authorize" value="yes"/>
</appSettings>
You can create your own AuthorizeWithConfig attribute that inherits from Authorize attribute:
public class AuthorizeWithConfigAttribute : AuthorizeAttribute
{
private readonly string _configKey;
public AuthorizeWithConfigAttribute(string configKey)
{
_configKey = configKey;
}
protected override bool IsAuthorized(HttpActionContext actionContext)
{
// Will be read from configuration
bool requireAuthorization;
// Skip authorization if
// (1) Found the specified key in app settings
// (2) Could parse app setting value into a boolean
// (3) App setting value is set to FALSE
var skipAuthorization =
ConfigurationManager.AppSettings.ContainsKey(configKey)
&& bool.TryParse(ConfigurationManager.AppSettings[configKey],
out requireAuthorization)
&& !requireAuthorization;
return skipAuthorization ? true : base.IsAuthorized(actionContext);
}
}
Then you can use it for your controller actions:
public class ProductsController : ApiController
{
[HttpGet, AuthorizeWithConfig("App:RequireAuthorization")]
public Product FindProduct(id) {}
}
Assuming that in app settings you have an App:RequireAuthorization setting that takes boolean values:
<appSettings>
<add key="App:RequireAuthorization" value="false"/>
</appSettings>
Is there any way to set the Parameter property from web.config?
public class TestHttpHandler : IHttpHandler
{
public bool IsReusable
{
get { return true; }
}
public string Parameter
{ get; set; }
public void ProcessRequest(HttpContext context)
{
context.Response.Write(Parameter);
}
}
If i do the following it just crashes.
<handlers>
<add name="Test" verb="*" path="/Manual/*"
type="Test.TestHttpHandler, Test" Parameter="test1234 "/>
</handlers>
Parameter is not a valid attribute for the handlers/add note. Just adding a property with the same name to your handler class doesn't make it magically work.
You can not pass a parameter directly in the handler definition, but within the code of your handler class you have full access to any other configuration data in web.config; any AppSetting or your own ConfigSections.
Is it possible to iterate the OutputCache keys? I know you can remove them individually via HttpResponse.RemoveOutputCacheItem(), but is there a way I can iterate all the keys to see what's in the collection?
I searched through Object Viewer, but didn't see anything.
Worst case, I can maintain my own index. Since I'm doing everything by VaryByCustom, they get "fed" through a method in global.asax. It just strikes me that there has to be a more elegant way of doing this.
If you're using ASP.NET 4.0 you could do this by writing your own OutputCacheProvider. That would give you the ability to store the keys at the point that the item is cached:
namespace StackOverflowOutputCacheProvider
{
public class SOOutputCacheProvider: OutputCacheProvider
{
public override object Add(string key, object entry, DateTime utcExpiry)
{
// Do something here to store the key
// Persist the entry object in a persistence layer somewhere e.g. in-memory cache, database, file system
return entry;
}
...
}
}
You'd then be able to read the keys out from wherever you've stored them.
This can be accomplished by inheriting MemoryCache and exposing the enumerator via a custom OutputCacheProvider implementation. Keep in mind that the enumerator locks the cache. Enumerating over the cache should be executed infrequently.
namespace Caching
{
internal class MemoryCacheInternal : System.Runtime.Caching.MemoryCache
{
public MemoryCacheInternal(string name, System.Collections.Specialized.NameValueCollection config = null) : base(name, config)
{
}
public System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string, object>> Enumerator()
{
return base.GetEnumerator();
}
}
}
Implement a custom OutputCacheProvider
using System.Web.Caching;
using System.Collections.Generic;
namespace Caching
{
public class EnumerableMemoryOutputCacheProvider : OutputCacheProvider, IEnumerable<KeyValuePair<string, object>>, IDisposable
{
private static readonly MemoryCacheInternal _cache = new MemoryCacheInternal("EnumerableMemoryOutputCache");
public override object Add(string key, object entry, System.DateTime utcExpiry)
{
return _cache.AddOrGetExisting(key, entry, UtcDateTimeOffset(utcExpiry));
}
public override object Get(string key)
{
return _cache.Get(key);
}
public override void Remove(string key)
{
_cache.Remove(key);
}
public override void Set(string key, object entry, System.DateTime utcExpiry)
{
_cache.Set(key, entry, UtcDateTimeOffset(utcExpiry));
}
public IEnumerator<KeyValuePair<string,object>> GetEnumerator()
{
return _cache.Enumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _cache.Enumerator();
}
private DateTimeOffset UtcDateTimeOffset(System.DateTime utcExpiry)
{
DateTimeOffset dtOffset = default(DateTimeOffset);
if ((utcExpiry.Kind == DateTimeKind.Unspecified)) {
dtOffset = DateTime.SpecifyKind(utcExpiry, DateTimeKind.Utc);
} else {
dtOffset = utcExpiry;
}
return dtOffset;
}
#region "IDisposable Support"
// To detect redundant calls
private bool disposedValue;
// IDisposable
protected virtual void Dispose(bool disposing)
{
if (!this.disposedValue) {
if (disposing) {
_cache.Dispose();
}
}
this.disposedValue = true;
}
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}
Configure the custom OutputCacheProvider
<system.web>
<caching>
<outputCache defaultProvider="EnumerableMemoryCache">
<providers>
<add name="EnumerableMemoryCache"
type="Caching.EnumerableMemoryOutputCacheProvider, MyAssemblyName"/>
</providers>
</outputCache>
<outputCacheSettings>
<outputCacheProfiles>
<add name="ContentAllParameters" enabled="false" duration="14400" location="Any" varyByParam="*"/>
</outputCacheProfiles>
</outputCacheSettings>
</caching>
</system.web>
Enumerate over the cache, in this case removing cache items.
OutputCacheProvider provider = OutputCache.Providers[OutputCache.DefaultProviderName];
if (provider == null) return;
IEnumerable<KeyValuePair<string, object>> keyValuePairs = provider as IEnumerable<KeyValuePair<string, object>>;
if (keyValuePairs == null) return;
foreach (var keyValuePair in keyValuePairs)
{
provider.Remove(keyValuePair.Key);
}
I have use this
http://www.codeproject.com/KB/session/exploresessionandcache.aspx
to view the cache and the session data. I only have to say that show only one pools data. If you have more pools, then you just see the one you are on it.
I have this code in my project:
var UI =
{
Layouts:
{
ShowLayoutSettings: function(pid, lid) {
My.PageServices.GetPageLayout(lid, pid, UI.Layouts._onShowLayoutSettings);
},
_onShowLayoutSettings: function(obj) {
alert(obj.ID);
}
}
}
and in my asp.net project a web service named PageServices:
namespace My
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
[System.Web.Script.Services.ScriptService]
public class PageServices : System.Web.Services.WebService
{
public PageServices()
{
}
[WebMethod]
[ScriptMethod(UseHttpGet = false, XmlSerializeString = true)]
[GenerateScriptType(typeof(PageLayout))]
public PageLayout GetPageLayout(string lid, int pid)
{
if (!SystemModel.UserManager.HasLogin())
return null;
var o = SystemModel.Layouts.GetPageLayout(pid);
o.Tag = lid;
return o;
}
}
}
I should mention that my PageLayout class is a linq class and it's serialization mode is Unidirectional.
and finally a anchor link:
Test
My problem is it is correct and sends ajax request to my service when I click this link and my service returns the object as needed, but it does not fire _onShowLayoutSettings as the call back function for this request.
I tested this work when I create a web servive which just returns and String object, and it was all correct, but I don't know why for my PageLayout object, it's not correct.
Please help me.
Thank you.
if it worked with returning a string, then you probably need to tell the ajax extension to create javascript code for the object you're trying to return. Add an attribute above your webmethod
[GenerateScriptType(typeof(PageLayout))]
where PageLayout is the name of the class the GetPageLayout returns
I found the solution, but it's very hard, I wrote a JavaScriptConverter named PageLayoutJavaScriptConverter like this:
public class PageLayoutJavaScriptConverter : JavaScriptConverter
{
public override IEnumerable<Type> SupportedTypes
{
get
{
return new Type[] { typeof(PageLayout) };
}
}
public override object Deserialize(
IDictionary<string, object> dictionary,
Type type,
JavaScriptSerializer serializer)
{
throw new NotImplementedException();
}
public override IDictionary<string, object> Serialize(
object obj,
JavaScriptSerializer serializer)
{
PageLayout e = obj as PageLayout;
Dictionary<string, object> result = new Dictionary<string, object>();
if (e != null)
{
result.Add("ID", e.ID);
}
return result;
}
}
and add this tag to web.config:
<system.web.extensions>
<scripting>
<webServices>
<jsonSerialization>
<converters>
<add name="PageLayoutJavaScriptConverter" type="WebApp.Code.PageLayoutJavaScriptConverter"/>
</converters>
</jsonSerialization>
</webServices>
<scriptResourceHandler enableCaching="true" enableCompression="true"/>
</scripting>
</system.web.extensions>
and all was correct.
I have a question, Isn't any other simpler way to do this work?
Is there any solution for accessing TempData property in the HttpResponseBase.WriteSubstitution() method
This does not work:
<%= Response.WriteSubstitution(x => Html.Encode(TempData["message"].ToString())) %>
But this works:
<%= Response.WriteSubstitution(x => DateTime.Now.ToString()) %>
The problem is in request processing for once cached pages. According to http://msdn.microsoft.com/en-us/library/system.web.httpresponse.writesubstitution.aspx:
On the first request to the page, the WriteSubstitution calls the HttpResponseSubstitutionCallback delegate to produce the output. Then, it adds a substitution buffer to the response, which retains the delegate to call on future requests. Finally, it degrades client-side cacheability from public to server-only, ensuring future requests to the page re-invoke the delegate by not caching on the client.
In other words the delegate does not have access to Session property (SessionStateTempDataProvider stores TempData in session) because there is no "normal" request life cycle. As I understand it is processed at HttpApplication.ResolveRequestCache/HttpApplication.PostResolveRequestCache event, but the current state is acquired at the HttpApplication.AcquireRequestState event (http://msdn.microsoft.com/en-us/library/ms178473.aspx)
Maybe I need some kind of "advanced custom TempDataProvider" :) Any ideas?
I've found the solution:
The main idea is in saving copy of TempData in the Cache and retreiving it on every request. The solution is a combination of custom TempDataProvider and simple http module. Plus there are couple of helpers and static classes.
Here is the code:
CustomTempDataProvider:
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Caching;
using System.Web.Mvc;
public class CustomTempDataProvider : SessionStateTempDataProvider, IHttpModule
{
public void Init(HttpApplication application)
{
application.BeginRequest += new EventHandler(application_BeginRequest);
}
void application_BeginRequest(object sender, EventArgs e)
{
var httpContext = HttpContext.Current;
var tempData = httpContext.Cache[TempDataKey] ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
httpContext.Items.Add("TempData", tempData);
httpContext.Cache.Remove(TempDataKey);
}
public override void SaveTempData(ControllerContext controllerContext,
IDictionary<string, object> values)
{
HttpContext.Current.Cache.Insert(TempDataKey, values, null, DateTime.Now.AddMinutes(5), Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, null);
base.SaveTempData(controllerContext, values);
}
public static string TempDataKey
{
get
{
string sessionID = "0";
var httpContext = HttpContext.Current;
if(httpContext.Session != null)
{
sessionID = httpContext.Session.SessionID;
}
else if (httpContext.Request.Cookies["ASP.NET_SessionId"] != null)
{
sessionID = httpContext.Request.Cookies["ASP.NET_SessionId"].Value;
}
return "TempData-For-Session-" + sessionID;
}
}
public void Dispose()
{
}
}
Register it in the web.config:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.web>
<httpModules>
<add name="CustomTempDataProvider" type="CustomTempDataProvider" />
</httpModules>
</system.web>
<system.webServer>
<modules>
<remove name="CustomTempDataProvider" />
<add name="CustomTempDataProvider" type="CustomTempDataProvider" />
</modules>
</system.webServer>
</configuration>
CustomControllerFactory:
using System.Web.Routing;
using System.Web.Mvc;
public class CustomControllerFactory : DefaultControllerFactory
{
public override IController CreateController(
RequestContext requestContext, string controllerName)
{
var controller = (Controller)base.CreateController(requestContext, controllerName);
controller.TempDataProvider = new CustomTempDataProvider();
return controller;
}
}
register it in the Global.asax:
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(typeof(CustomControllerFactory));
}
Static class for accessing TempData:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
public static class CustomTempData
{
public static object Get(string key)
{
var tempData = HttpContext.Current.Items["TempData"] as IDictionary<string, object>;
var item = tempData.FirstOrDefault(x => x.Key == key).Value ?? String.Empty;
return item;
}
}
Helper for Post-cache Substitution:
using System;
using System.Web;
using System.Web.Mvc;
public static class Html
{
public delegate object MvcResponseSubstitutionCallback(HttpContextBase context);
public static object MvcResponseSubstitute(this HtmlHelper html, MvcResponseSubstitutionCallback callback)
{
html.ViewContext.HttpContext.Response.WriteSubstitution(
context =>
HttpUtility.HtmlEncode(
(callback(new HttpContextWrapper(context)) ?? String.Empty).ToString()
)
);
return null;
}
}
Now this works successfully:
<h3><%= Html.MvcResponseSubstitute(context => CustomTempData.Get("message")) %></h3>
If you understand russian, read this
Hope this helps