NopCommerce 3.80 - Dashboard chart weekdays language - nopcommerce

Is it possible to change the language (to Portuguese) of the weekdays presented in the dashboard charts?
NopCommerce v3.80

Those labels come from the server, more precisely from the date = searchWeekDateUser.Date.ToString("d dddd"), line inside the LoadOrderStatistics method in Nop.Admin.Controllers.OrderController class (line 4260)
The easy way to fix this should be to change the locale at application level for the admin area but there is an open issue about that solution
There are other alternatives such as:
Change the source code line to take the "pt" locale, recompile and redeploy
date = searchWeekDateUser.Date.ToString("d dddd",new CultureInfo("pt")),
Create and register a new global ActionFilter, you can set the culture there just for the controller and action you need:
public class Culture : ActionFilterAttribute
{
private string _actionName;
private string _controllerName;
private string _culture;
public Culture(string culture)
{
_culture = culture;
}
public Culture(string culture,string controllerName,string actionName)
{
_culture = culture;
_controllerName = controllerName;
_actionName = actionName;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!String.IsNullOrEmpty(_culture) &&
(_controllerName == null || filterContext.ActionDescriptor.ControllerDescriptor.ControllerName.Equals(_controllerName)) &&
(_actionName == null || filterContext.ActionDescriptor.ActionName.Equals(_actionName)))
{
System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(_culture);
System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(_culture);
}
}
}

Related

Get Custom Attribute on ASMX Web Service from HTTP Module

Here's the situation:
Legacy ASP.NET product. A lot of old ASMX services (among other types of endpoints - ASPX, ASHX, etc).
We're enhancing some security logic. Part of the changes dictate defining the application module to which each ASMX service belongs. We plan to use the custom attribute shown below for this purpose.
[AttributeUsage(AttributeTargets.Class)]
public class ModuleAssignmentAttribute : Attribute
{
public Module[] Modules { get; set; }
public ModuleAssignmentAttribute(params Module[] modules)
{
Modules = modules;
}
}
Below is a sample of how the module will be applied to an ASMX service.
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ModuleAssignment(Module.ApplicationModuleA)]
public class SomeService : System.Web.Services.WebService
{
[WebMethod(true)]
public string GetValue()
{
return "Some Service Value";
}
}
The HTTP module below will be used to authorize access to the service.
public class MyAuthorizationModule : IHttpModule
{
public void Dispose()
{
//clean-up code here.
}
public void Init(HttpApplication context)
{
context.PreRequestHandlerExecute += new EventHandler(OnAuthorizeRequest);
}
public void OnAuthorizeRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Handler == null) return;
Attribute att = Attribute.GetCustomAttribute(HttpContext.Current.Handler.GetType(), typeof(ModuleAssignmentAttribute));
if (att != null)
{
Module[] modules = ((ModuleAssignmentAttribute)att).Modules;
// Simulate getting the user's active role ID
int roleId = 1;
// Simulate performing an authz check
AuthorizationAgent agent = new AuthorizationAgent();
bool authorized = agent.AuthorizeRequest(roleId, modules);
if (!authorized)
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.StatusCode = 401;
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
}
}
The problem is that, for ASMX web services, the following line of code from the HTTP module returns null (note that this works for ASPX pages).
Attribute att = Attribute.GetCustomAttribute(HttpContext.Current.Handler.GetType(), typeof(ModuleAssignmentAttribute));
The value of HttpContext.Current.Handler.GetType() in this situation is "System.Web.Script.Services.ScriptHandlerFactory+HandlerWrapperWithSession". That type is apparently unaware of the custom attribute defined on the ASMX service.
Any ideas on how to get the custom attribute from the ASMX service type in this scenario?
Here's a solution to the problem. Requires reflection. Ugly and fragile code - I wouldn't recommend using it if you don't have to. I'd be interested to know if I'm overlooking a more elegant way.
public void Init(HttpApplication context)
{
context.PreRequestHandlerExecute += new EventHandler(OnAuthorizeRequest);
}
public void OnAuthorizeRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Handler == null) return;
Attribute att = null;
// ScriptHandlerFactory+HandlerWrapperWithSession is the type of handler for ASMX web service calls to web methods that use session.
// This class is internal, so need to do a string comparison here (is there another way?).
if (HttpContext.Current.Handler.GetType().ToString() == "System.Web.Script.Services.ScriptHandlerFactory+HandlerWrapperWithSession")
{
// HandlerWrapperWithSession has a protected field named "_originalHandler" that it inherits from HandlerWrapper.
FieldInfo originalHandlerField = HttpContext.Current.Handler.GetType().GetField("_originalHandler",BindingFlags.NonPublic | BindingFlags.Instance);
object originalHandler = originalHandlerField.GetValue(HttpContext.Current.Handler);
// The _originalHandler value is an instance of SyncSessionHandler.
// The inheritance tree for SyncSessionHandler is:
//
// WebServiceHandler
// ----> SyncSessionlessHandler
// ----> SyncSessionHandler
//
// We need to walk the tree up to the WebServiceHandler class.
bool exitLoop = false;
Type t = originalHandler.GetType();
while (t != null)
{
// WebServiceHandler is internal, so again another string comparison.
if (t.ToString() == "System.Web.Services.Protocols.WebServiceHandler")
{
// WebServiceHandler has a private field named protocol. This field has the type HttpGetServerProtocol.
FieldInfo protocolField = t.GetField("protocol", BindingFlags.NonPublic | BindingFlags.Instance);
object protocolValue = protocolField.GetValue(originalHandler);
// The inheritance tree for ServerProtocol is:
//
// HttpServerProtocol
// ----> HttpGetServerProtocol
//
// We need to walk the three up to the HttpServerProtocol class.
Type t2 = protocolValue.GetType();
while (t2 != null)
{
if (t2.ToString() == "System.Web.Services.Protocols.HttpServerProtocol")
{
// HttpServerProtocol has an internal property named MethodInfo. This property has the type LogicalMethodInfo.
PropertyInfo methodInfoProperty = t2.GetProperty("MethodInfo", BindingFlags.NonPublic | BindingFlags.Instance);
object methodInfoValue = methodInfoProperty.GetValue(protocolValue);
// The LogicalMethodInfo class has a DeclaringType property. This property stores the type of the ASMX service.
att = Attribute.GetCustomAttribute(((LogicalMethodInfo)methodInfoValue).DeclaringType, typeof(ModuleAssignmentAttribute));
exitLoop = true;
break;
}
t2 = t2.BaseType;
}
}
if (exitLoop) break;
t = t.BaseType;
}
}
else
{
att = Attribute.GetCustomAttribute(HttpContext.Current.Handler.GetType(), typeof(ModuleAssignmentAttribute));
}
if (att != null)
{
Module[] modules = ((ModuleAssignmentAttribute)att).Modules;
// Simulate getting the user's active role ID
int roleId = 1;
// Simulate performing an authz check
AuthorizationAgent agent = new AuthorizationAgent();
bool authorized = agent.AuthorizeRequest(roleId, modules);
if (!authorized)
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.StatusCode = 401;
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
}
}
I had to use Reflector + runtime debugging to find this solution.

Accessing OutArgument value of Receive implementation child activity within custom WF4 activity

Using VS2012/.NET 4.5 I am creating a custom activity which implements a Receive child activity (as an implementation child). The parameters are in the example below fixed to just one: OutValue of type Guid.
I really would love to access the value of incoming parameter value in ReceiveDone, because I need to work with it and transform it before returning it from the activity. Please ignore that I am currently using a Guid, it still fails to access the value with and InvalidOperationException:
An Activity can only get the location of arguments which it owns. Activity 'TestActivity' is trying to get the location of argument 'OutValue' which is owned by activity 'Wait for
workflow start request [Internal for TestActivity]'
I have tried everything I could think of, but am stupefied. There must be a way to do this very simple thing?
public class TestActivity : NativeActivity<Guid>
{
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
var content = ReceiveParametersContent.Create(new Dictionary<string, OutArgument>()
{
// How to access the runtime value of this inside TestActivity?
{"OutValue", new OutArgument<Guid>()}
});
startReceiver = new Receive()
{
DisplayName = string.Format("Wait for workflow start request [Internal for {0}]", this.DisplayName),
CanCreateInstance = true,
ServiceContractName = XName.Get("IStartService", Namespace),
OperationName = "Start",
Content = content
};
foreach (KeyValuePair<string, OutArgument> keyValuePair in content.Parameters)
{
metadata.AddImportedChild(keyValuePair.Value.Expression);
}
metadata.AddImplementationChild(startReceiver);
}
protected override void Execute(NativeActivityContext context)
{
context.ScheduleActivity(startReceiver, ReceiveDone);
}
private void ReceiveDone(NativeActivityContext context, ActivityInstance completedInstance)
{
var receive = completedInstance.Activity as Receive;
ReceiveParametersContent content = receive.Content as ReceiveParametersContent;
try
{
// This causes InvalidOperationException.
// An Activity can only get the location of arguments which it owns.
// Activity 'TestActivity' is trying to get the location of argument 'OutValue'
// which is owned by activity 'Wait for workflow start request [Internal for TestActivity]'
var parmValue = content.Parameters["OutValue"].Get(context);
}
catch (Exception)
{ }
}
private Receive startReceiver;
private const string Namespace = "http://company.namespace";
}
Use internal variables to pass values between internal activities.
Although not directly related to your code, see the example below which should give you the idea:
public sealed class CustomNativeActivity : NativeActivity<int>
{
private Variable<int> internalVar;
private Assign<int> internalAssign;
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
base.CacheMetadata(metadata);
internalVar = new Variable<int>("intInternalVar", 10);
metadata.AddImplementationVariable(internalVar);
internalAssign = new Assign<int>
{
To = internalVar,
Value = 12345
};
metadata.AddImplementationChild(internalAssign);
}
protected override void Execute(NativeActivityContext context)
{
context.ScheduleActivity(internalAssign, (activityContext, instance) =>
{
// Use internalVar value, which was seted by previous activity
var value = internalVar.Get(activityContext);
Result.Set(activityContext, value);
});
}
}
Calling the above activity:
WorkflowInvoker.Invoke<int>(new CustomNativeActivity());
Will output:
12345
Edit:
In your case your OutArgument will be the internalVar
new OutArgument<int>(internalVar);
You need to use OutArgument and them to variables. See the code example with the documentation.
I may have tried everything I thought of, but I am stubborn and refuse to give up, so I kept on thinking ;)
I here have changed my example to use a Data class as a parameter instead (it does not change anything in itself, but I needed that in my real world example).
This code below is now a working example on how to access the incoming data. The use of an implementation Variable is the key:
runtimeVariable = new Variable<Data>();
metadata.AddImplementationVariable(runtimeVariable);
And the OutArgument:
new OutArgument<Data>(runtimeVariable)
I can then access the value with:
// Here dataValue will get the incoming value.
var dataValue = runtimeVariable.Get(context);
I haven't seen an example elsewhere, which does exactly this. Hope it will be of use to any one but me.
The code:
[DataContract]
public class Data
{
[DataMember]
Guid Property1 { get; set; }
[DataMember]
int Property2 { get; set; }
}
public class TestActivity : NativeActivity<Guid>
{
public ReceiveContent Content { get; set; }
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
runtimeVariable = new Variable<Data>();
metadata.AddImplementationVariable(runtimeVariable);
Content = ReceiveParametersContent.Create(new Dictionary<string, OutArgument>()
{
{"OutValue", new OutArgument<Data> (runtimeVariable)}
});
startReceiver = new Receive()
{
DisplayName = string.Format("Wait for workflow start request [Internal for {0}]", this.DisplayName),
CanCreateInstance = true,
ServiceContractName = XName.Get("IStartService", Namespace),
OperationName = "Start",
Content = Content
};
metadata.AddImplementationChild(startReceiver);
}
protected override void Execute(NativeActivityContext context)
{
context.ScheduleActivity(startReceiver, ReceiveDone);
}
private void ReceiveDone(NativeActivityContext context, ActivityInstance completedInstance)
{
// Here dataValue will get the incoming value.
var dataValue = runtimeVariable.Get(context);
}
private Receive startReceiver;
private Variable<Data> runtimeVariable;
private const string Namespace = "http://company.namespace";
}

Create the own VirtualPathProvider in MVC4?

I'm suffering trying to get some views from a library to the main project. I was starting to read about creating your own VirtualPathProvider implementation here: Using VirtualPathProvider to load ASP.NET MVC views from DLLs
I had to set my view = EmbbebedResource to get the resource from the library. But now is throwing another error.
In the header of my partial view I had the following:
#model Contoso.ExercisesLibrary.AbsoluteArithmetic.Problem1
And the error says: External component has thrown an exception. c:\Users\Oscar\AppData\Local\Temp\Temporary ASP.NET Files\root\4f78c765\7f9a47c6\App_Web_contoso.exerciseslibrary.absolutearithmetic.view1.cshtml.38e14c22.y-yjyt6g.0.cs(46): error CS0103: The name 'model' does not exist in the current context
I don't know why the compiler tells that cannot recognized my model. When I'm in design mode, I can see the compiler that the check is all right.
Check the image
What am I doing wrong o what am I missing?
Thanks in advance.
Try adding an #inherits directive to the top of your razor view:
#inherits System.Web.Mvc.WebViewPage
#model Contoso.ExercisesLibrary.AbsoluteArithmetic.Problem1
The reason you need this is because your view comes from an embedded resource and not from the standard ~/Views location. And as you know inside ~/Views there's a file called web.config. And inside this file there's a pageBaseType="System.Web.Mvc.WebViewPage" directive indicating that all Razor files inside ~/Views should inherit from this base type. But since your view is now coming from an unknown location you have nowhere specified that it should be a System.Web.Mvc.WebViewPage. And all the MVC specific stuff such as models, HTML helpers, ... are defined in this base class.+
I faced this issue "The name 'model' does not exist in the current context". What I did was added same "areas" folder structure (from my embedded mvc project) to my main MVC project (Areas/AnualReports/Views/) and copied web.config (default web.config from views folder, not the one from root) to Views folder which solved the issue. I am not sure this will work in your case.
Update:
Adding web.config (from views folder) to root "areas" folder in main MVC project also works.
I have the same problem as you so after all searches I got working solution
Create your own WebViewPage based abstract class (generic for model and non generic)
public abstract class MyOwnViewPage<TModel> : WebViewPage<TModel> { }
public abstract class MyOwnViewPage : WebViewPage { }
Next create VirtualFile based class or embedded view's
class AssemblyResourceFile : VirtualFile
{
private readonly IDictionary<string, Assembly> _nameAssemblyCache;
private readonly string _assemblyPath;
private readonly string _webViewPageClassName;
public string LayoutPath { get; set; }
public string ViewStartPath { get; set; }
public AssemblyResourceFile(IDictionary<string, Assembly> nameAssemblyCache, string virtualPath) :
base(virtualPath)
{
_nameAssemblyCache = nameAssemblyCache;
_assemblyPath = VirtualPathUtility.ToAppRelative(virtualPath);
LayoutPath = "~/Views/Shared/_Layout.cshtml";
ViewStartPath = "~/Views/_ViewStart.cshtml";
_webViewPageClassName = typeofMyOwnViewPage).ToString();
}
// Please change Open method for your scenario
public override Stream Open()
{
string[] parts = _assemblyPath.Split(new[] { '/' }, 4);
string assemblyName = parts[2];
string resourceName = parts[3].Replace('/', '.');
Assembly assembly;
lock (_nameAssemblyCache)
{
if (!_nameAssemblyCache.TryGetValue(assemblyName, out assembly))
{
var assemblyPath = Path.Combine(HttpRuntime.BinDirectory, assemblyName);
assembly = Assembly.LoadFrom(assemblyPath);
_nameAssemblyCache[assemblyName] = assembly;
}
}
Stream resourceStream = null;
if (assembly != null)
{
resourceStream = assembly.GetManifestResourceStream(resourceName);
if (resourceName.EndsWith(".cshtml"))
{
//the trick is here. We must correct our embedded view
resourceStream = CorrectView(resourceName, resourceStream);
}
}
return resourceStream;
}
public Stream CorrectView(string virtualPath, Stream stream)
{
var reader = new StreamReader(stream, Encoding.UTF8);
var view = reader.ReadToEnd();
stream.Close();
var ourStream = new MemoryStream();
var writer = new StreamWriter(ourStream, Encoding.UTF8);
var modelString = "";
var modelPos = view.IndexOf("#model");
if (modelPos != -1)
{
writer.Write(view.Substring(0, modelPos));
var modelEndPos = view.IndexOfAny(new[] { '\r', '\n' }, modelPos);
modelString = view.Substring(modelPos, modelEndPos - modelPos);
view = view.Remove(0, modelEndPos);
}
writer.WriteLine("#using System.Web.Mvc");
writer.WriteLine("#using System.Web.Mvc.Ajax");
writer.WriteLine("#using System.Web.Mvc.Html");
writer.WriteLine("#using System.Web.Routing");
var basePrefix = "#inherits " + _webViewPageClassName;
if (virtualPath.ToLower().Contains("_viewstart"))
{
writer.WriteLine("#inherits System.Web.WebPages.StartPage");
}
else if (modelString == "#model object")
{
writer.WriteLine(basePrefix + "<dynamic>");
}
else if (!string.IsNullOrEmpty(modelString))
{
writer.WriteLine(basePrefix + "<" + modelString.Substring(7) + ">");
}
else
{
writer.WriteLine(basePrefix);
}
writer.Write(view);
writer.Flush();
ourStream.Position = 0;
return ourStream;
}
}
Next create VirtualPathProvider based class (modify it for your purposes)
public class AssemblyResPathProvider : VirtualPathProvider
{
private readonly Dictionary<string, Assembly> _nameAssemblyCache;
private string _layoutPath;
private string _viewstartPath;
public AssemblyResPathProvider(string layout, string viewstart)
{
_layoutPath = layout;
_viewstartPath = viewstart;
_nameAssemblyCache = new Dictionary<string, Assembly>(StringComparer.InvariantCultureIgnoreCase);
}
private bool IsAppResourcePath(string virtualPath)
{
string checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
bool bres1 = checkPath.StartsWith("~/App_Resource/",
StringComparison.InvariantCultureIgnoreCase);
bool bres2 = checkPath.StartsWith("/App_Resource/",
StringComparison.InvariantCultureIgnoreCase);
//todo: fix this
if (checkPath.EndsWith("_ViewStart.cshtml"))
{
return false;
}
if (checkPath.EndsWith("_ViewStart.vbhtml"))
{
return false;
}
return ((bres1 || bres2));
}
public override bool FileExists(string virtualPath)
{
return (IsAppResourcePath(virtualPath) ||
base.FileExists(virtualPath));
}
public override VirtualFile GetFile(string virtualPath)
{
if (IsAppResourcePath(virtualPath))
{
// creating AssemblyResourceFile instance
return new AssemblyResourceFile(_nameAssemblyCache, virtualPath,_layoutPath,virtualPath);
}
return base.GetFile(virtualPath);
}
public override CacheDependency GetCacheDependency(
string virtualPath,
IEnumerable virtualPathDependencies,
DateTime utcStart)
{
if (IsAppResourcePath(virtualPath))
{
return null;
}
return base.GetCacheDependency(virtualPath,
virtualPathDependencies, utcStart);
}
}
At last register your AssemblyResPathProvider in global.asax
string _layoutPath = "~/Views/Shared/_Layout.cshtml";
string _viewstarPath = "~/Views/_ViewStart.cshtml";
HostingEnvironment.RegisterVirtualPathProvider(new AssemblyResPathProvider(_layoutPath,_viewstarPath));
This is not ideal solution but its working for me good. Cheers!
In my case, the solution was to make the virtual Path start with "~Views/" - just like any normal view.
Not working: ~/VIRTUAL/Home/Index.cshtml
Working: ~/Views/VIRTUAL/Home/Index.cshtml
I think, this has to do with the web.config lying around in ~/Views and defining a lot of stuff for the views. Maybe anybody can give more information.
Hope that helps anyway.

Refer to session variable without casting it to its type everytime

I have a session variable that is a class instance. I declared it in Global.asax:
void Session_Start(object sender, EventArgs e)
{
// Code that runs when a new session is started
Session["SiteVariables"] = new SiteVariables();
}
Now I need to refer to some of the members of this variable in several places of my solution but, as far as I understand, I have to do it this way:
SiteVariables objSiteVariables = (SiteVariables)Session["SiteVariables"];
Label1.Text = objSiteVariables.permiss;
I wonder if there is a way, e.g. setting up somewhere a static variable or something, that allows me just to do:
Label1.Text = objSiteVariables.permiss;
in any place of my project.
Thank you!
You may create your own helper type/method.
public class Util
{
public static SiteVariables Variables
{
get
{
return HttpContext.Current.Session["SiteVariables"] as SiteVariables;
}
}
}
And assign value to
Label1.Text=Util.Variables.permiss;
You can create a wrapper for the session, which will allow type safe access like this:
public class SessionHandler
{
public static SessionHandler CurrentSession
{
get
{
SessionHandler session =
(SessionHandler)HttpContext.Current.Session["SessionId"];
if (session == null)
{
session = new SessionHandler();
HttpContext.Current.Session["SessionId"] = session;
}
return session;
}
}
public SiteVariables SiteVariables { get; set; }
}
You can use it like this:
// assign
SessionHandler.CurrentSession.SiteVariables = new SiteVariables();
// retrieve
SiteVariables objSiteVariables = SessionHandler.CurrentSession.SiteVariables;
This way you can add more sessions just by adding more property to the SessionHandler class.

ViewState as Attribute

Instead of this ..
public string Text
{
get { return ViewState["Text"] as string; }
set { ViewState["Text"] = value; }
}
I would like this ..
[ViewState]
public String Text { get; set; }
Can it be done?
Like this:
public class BasePage: Page {
protected override Object SaveViewState() {
object baseState = base.SaveViewState();
IDictionary<string, object> pageState = new Dictionary<string, object>();
pageState.Add("base", baseState);
// Use reflection to iterate attributed properties, add
// each to pageState with the property name as the key
return pageState;
}
protected override void LoadViewState(Object savedState) {
if (savedState != null) {
var pageState = (IDictionary<string, object>)savedState;
if (pageState.Contains("base")) {
base.LoadViewState(pageState["base"]);
}
// Iterate attributed properties. If pageState contains an
// item with the appropriate key, set the property value.
}
}
}
Pages that inherit from this class could use the attribute-driven syntax you've proposed.
Well, this is what i got so far, TY Jeff for pointing me in the right direction:
TestPage:
public partial class Pages_Test : BasePage {
[ViewState]
public String Name { get; set; }
BasePage:
#region Support ViewState Attribute
BindingFlags _flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
protected override Object SaveViewState()
{
object _baseState = base.SaveViewState();
IDictionary<string, object> _pageState = new Dictionary<string, object> { { "base", _baseState } };
//Use reflection to get properties marked for viewstate
foreach (PropertyInfo _property in GetType().GetProperties(_flags))
{
if (_property.HasAttribute<ViewState>())
{
object _value = _property.GetValue(this, _flags , null, null, null);
_pageState.Add(new KeyValuePair<string, object>(_property.Name, _value));
}
}
return _pageState;
}
protected override void LoadViewState(Object savedState)
{
if (savedState != null)
{
var _pageState = (IDictionary<string, object>)savedState;
if (_pageState.ContainsKey("base"))
{
base.LoadViewState(_pageState["base"]);
}
//use reflection to set properties
foreach (PropertyInfo _property in GetType().GetProperties(_flags ))
{
if (_property.HasAttribute<ViewState>() && _pageState.ContainsKey(_property.Name))
{
object _value = _pageState[_property.Name];
_property.SetValue(this, _value, _flags , null, null, null);
}
}
}
}
#endregion
Attribute:
/// <summary>
/// This attribute is used by the BasePage to identify properties that should be persisted to ViewState
/// Note: Private properties are not supported
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ViewState : Attribute
{
//Marker
}
Helpers:
public static class PropertyExtension
{
public static Boolean HasAttribute<T>(this PropertyInfo property)
{
object[] attrs = property.GetCustomAttributes(typeof(T), false);
return attrs != null && attrs.Length == 1;
}
}
EDIT
Jan has a valid point about performance, I did some profiling with the following results:
Without Attribute With Attribute Increase Slower %
One Property
First Load 0,004897899 0,010734255 0,005836356 219
Save, postback 0,002353861 0,010478008 0,008124147 445
Load, Postback 0,001488807 0,00627482 0,004786013 421
10 properties
First Load 0,006184096 0,015288675 0,009104579 247
Save, postback 0,004061759 0,015052262 0,010990503 371
Load, Postback 0,0015708 0,005833074 0,004262274 371
% increase
Avg Page. 0,902215714567075 0,00648
On a Empty page the increase is considerable, but on an average page with a load of 1s this increase amounts to 0,01%.
Update : Using PostSharp, PostSharp4ViewState
Step 1 : Make sure your website is precompiled
Step 2 : Install PostSharp and PostSharp4ViewState
Step 3 : Reference PostSharp.Public And PostSharp4ViewState
Step 4 : Following is Code is now valid.
[Persist(Mode=PersistMode.ViewState)]
private string _name;
public String Name {
get { return _name; }
set { _name = value; }
}
BBorg's solution is actually incredibly slow because of the heavy use of reflection.
Using PostSharp.Laos, by letting your attribute inherit from OnMethodBoundaryAspect, you can easily override public override void OnInvocation(MethodInvocationEventArgs eventArgs) and do all the magic in there. This will be way faster. Check for example the CacheAttribute example on the PostSharp homepage.
If you are really wanting bare speed, you can write a PostSharp plugin that weaves MSIL (GetFromViewState, SetInViewState methods or something) into your properties, that won't even have a performance penalty.
This functionality is built into NHibernate Burrow. If you don't happen to use NHibernate in your application, the source code for NHibernate Burrow is available here. Feel free to dig in, see how they did it, and rip out any parts that our useful to you (as long as you comply with the LGPL license).
The most relevant code seems to be in StatefulFieldProcessor.cs lines 51 - 72.
/// <summary>
/// Get the FieldInfo - Attribute pairs that have the customer attribute of type <typeparamref name="AT"/>
/// </summary>
/// <typeparam name="AT"></typeparam>
/// <returns></returns>
protected IDictionary<FieldInfo, AT> GetFieldInfo<AT>() where AT : Attribute {
IDictionary<FieldInfo, AT> retVal = new Dictionary<FieldInfo, AT>();
foreach (FieldInfo fi in GetFields())
foreach (AT a in Attribute.GetCustomAttributes(fi, typeof (AT)))
retVal.Add(fi, a);
return retVal;
}
protected IDictionary<FieldInfo, StatefulField> GetStatefulFields() {
IDictionary<FieldInfo, StatefulField> retVal;
Type controlType = Control.GetType();
if (controlType.Assembly == webAssembly)
return null;
if (!fieldInfoCache.TryGetValue(controlType, out retVal))
fieldInfoCache[controlType] = retVal = GetFieldInfo<StatefulField>();
return retVal;
}

Resources