Get Custom Attribute on ASMX Web Service from HTTP Module - asp.net

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.

Related

Avoid dynamic invocation when processing a bus event that implements a generic interface

I have recently completed a course about Microservices and RabbitMQ using ASP.NET Core and noticed that event processing uses dynamic invocation. The code is the following (only relevant parts retained ):
public sealed class RabbitMqBus : IEventBus
{
private readonly IMediator _mediator;
private readonly Dictionary<string, List<Type>> _handlers;
private readonly List<Type> _eventTypes;
}
public void Subscribe<TEvent, THandler>() where TEvent : Event where THandler : IEventHandler<TEvent>
{
string eventName = typeof(TEvent).Name;
var handlerType = typeof(THandler);
if (!_eventTypes.Contains(typeof(TEvent)))
_eventTypes.Add(typeof(TEvent));
if (!_handlers.ContainsKey(eventName))
_handlers.Add(eventName, new List<Type>());
if (_handlers[eventName].Any(s => s == handlerType))
throw new ArgumentException($"Handler type {handlerType.Name} is already registered for {eventName}");
_handlers[eventName].Add(handlerType);
StartBasicConsume<TEvent>();
}
private async Task ConsumerReceived(object sender, BasicDeliverEventArgs e)
{
string eventName = e.RoutingKey;
string message = Encoding.UTF8.GetString(e.Body);
try
{
await ProcessEvent(eventName, message);
}
catch (Exception exception)
{
Console.WriteLine(exception);
throw;
}
}
private async Task ProcessEvent(string eventName, string message)
{
if (_handlers.ContainsKey(eventName))
{
using var scope = _serviceScopeFactory.CreateScope();
var subscriptions = _handlers[eventName];
foreach (var subscription in subscriptions)
{
var handler = scope.ServiceProvider.GetService(subscription);
if (handler == null)
continue;
//TODO: check if this can be made typed and avoid messy dynamic invocation at the end
Type eventType = _eventTypes.SingleOrDefault(t => t.Name == eventName);
object #event = JsonConvert.DeserializeObject(message, eventType);
Type concreteType = typeof(IEventHandler<>).MakeGenericType(eventType);
await (Task) concreteType.GetMethod("Handle").Invoke(handler, new[] {#event});
}
}
}
public interface IEventHandler
{
}
public interface IEventHandler<in TEvent>: IEventHandler
where TEvent: Event
{
Task Handle(TEvent #event);
}
My issue is with the code following the TODO because it relies on reflection and dynamic invocation of a method with a clear name ("Handle").
My first improvement is eliminating the magic string through nameof:
Type eventType = _eventTypes.SingleOrDefault(t => t.Name == eventName);
Event #event = (Event) JsonConvert.DeserializeObject(message, eventType);
Type concreteType = typeof(IEventHandler<>).MakeGenericType(eventType);
await (Task)concreteType.GetMethod(nameof(IEventHandler<Event>.Handle)).Invoke(handler, new object[] { #event });
However, reflection is still used. I understand that this is required because Handle is defined as a generic method (part of a generic interface) and the received event is dynamically constructed.
Is there a way to refactor this code to avoid reflection?

Web API Return OAuth Token as XML

Using the default Visual Studio 2013 Web API project template with individual user accounts, and posting to the /token endpoint with an Accept header of application/xml, the server still returns the response in JSON:
{"access_token":"...","token_type":"bearer","expires_in":1209599}
Is there a way to get the token back as XML?
According to RFC6749 the response format should be JSON and Microsoft implemented it accordingly. I found out that JSON formatting is implemented in Microsoft.Owin.Security.OAuth.OAuthAuthorizationServerHandler internal class with no means of extension.
I also encountered the need to have token response in XML.
The best solution I came up with was to implement HttpModule converting JSON to XML when stated in Accept header.
public class OAuthTokenXmlResponseHttpModule : IHttpModule
{
private static readonly string FilterKey = typeof(OAuthTokenXmlResponseHttpModule).Name + typeof(MemoryStreamFilter).Name;
public void Init(HttpApplication application)
{
application.BeginRequest += ApplicationOnBeginRequest;
application.EndRequest += ApplicationOnEndRequest;
}
private static void ApplicationOnBeginRequest(object sender, EventArgs eventArgs)
{
var application = (HttpApplication)sender;
if (ShouldConvertToXml(application.Context.Request) == false) return;
var filter = new MemoryStreamFilter(application.Response.Filter);
application.Response.Filter = filter;
application.Context.Items[FilterKey] = filter;
}
private static bool ShouldConvertToXml(HttpRequest request)
{
var isTokenPath = string.Equals("/token", request.Path, StringComparison.InvariantCultureIgnoreCase);
var header = request.Headers["Accept"];
return isTokenPath && (header == "text/xml" || header == "application/xml");
}
private static void ApplicationOnEndRequest(object sender, EventArgs eventArgs)
{
var context = ((HttpApplication) sender).Context;
var filter = context.Items[FilterKey] as MemoryStreamFilter;
if (filter == null) return;
var jsonResponse = filter.ToString();
var xDocument = JsonConvert.DeserializeXNode(jsonResponse, "oauth");
var xmlResponse = xDocument.ToString(SaveOptions.DisableFormatting);
WriteResponse(context.Response, xmlResponse);
}
private static void WriteResponse(HttpResponse response, string xmlResponse)
{
response.Clear();
response.ContentType = "application/xml;charset=UTF-8";
response.Write(xmlResponse);
}
public void Dispose()
{
}
}
public class MemoryStreamFilter : Stream
{
private readonly Stream _stream;
private readonly MemoryStream _memoryStream = new MemoryStream();
public MemoryStreamFilter(Stream stream)
{
_stream = stream;
}
public override void Flush()
{
_stream.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
return _stream.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count)
{
_memoryStream.Write(buffer, offset, count);
_stream.Write(buffer, offset, count);
}
public override string ToString()
{
return Encoding.UTF8.GetString(_memoryStream.ToArray());
}
#region Rest of the overrides
public override bool CanRead
{
get { throw new NotImplementedException(); }
}
public override bool CanSeek
{
get { throw new NotImplementedException(); }
}
public override bool CanWrite
{
get { throw new NotImplementedException(); }
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override long Length
{
get { throw new NotImplementedException(); }
}
public override long Position
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
#endregion
}
Ok I had such a fun time trying to figure this out using OWIN I thought I would share my solution with the community, I borrowed some insight from other posts https://stackoverflow.com/a/26216511/1148288 and https://stackoverflow.com/a/29105880/1148288 along with the concepts Alexei describs in his post. Nothing fancy doing with implementation but I had a requirement for my STS to return an XML formatted response, I wanted to keep with the paradigm of honoring the Accept header, so my end point would examine that to determine if it needed to run the XML swap or not. This is what I am current using:
private void ConfigureXMLResponseSwap(IAppBuilder app)
{
app.Use(async (context, next) =>
{
if (context.Request != null &&
context.Request.Headers != null &&
context.Request.Headers.ContainsKey("Accept") &&
context.Request.Headers.Get("Accept").Contains("xml"))
{
//Set a reference to the original body stream
using (var stream = context.Response.Body)
{
//New up and set the response body as a memory stream which implements the ability to read and set length
using (var buffer = new MemoryStream())
{
context.Response.Body = buffer;
//Allow other middlewares to process
await next.Invoke();
//On the way out, reset the buffer and read the response body into a string
buffer.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(buffer))
{
string responsebody = await reader.ReadToEndAsync();
//Using our responsebody string, parse out the XML and add a declaration
var xmlVersion = JsonConvert.DeserializeXNode(responsebody, "oauth");
xmlVersion.Declaration = new XDeclaration("1.0", "UTF-8", "yes");
//Convert the XML to a byte array
var bytes = Encoding.UTF8.GetBytes(xmlVersion.Declaration + xmlVersion.ToString());
//Clear the buffer bits and write out our new byte array
buffer.SetLength(0);
buffer.Write(bytes, 0, bytes.Length);
buffer.Seek(0, SeekOrigin.Begin);
//Set the content length to the new buffer length and the type to an xml type
context.Response.ContentLength = buffer.Length;
context.Response.ContentType = "application/xml;charset=UTF-8";
//Copy our memory stream buffer to the output stream for the client application
await buffer.CopyToAsync(stream);
}
}
}
}
else
await next.Invoke();
});
}
Of course you would then wire this up during startup config like so:
public void Configuration(IAppBuilder app)
{
HttpConfiguration httpConfig = new HttpConfiguration();
//Highly recommend this is first...
ConfigureXMLResponseSwap(app);
...more config stuff...
}
Hope that helps any other lost souls that find there way to the this post seeking to do something like this!
take a look here i hope it can help how to set a Web API REST service to always return XML not JSON
Could you retry by doing the following steps:
In the WebApiConfig.Register(), specify
config.Formatters.XmlFormatter.UseXmlSerializer = true;
var supportedMediaTypes = config.Formatters.XmlFormatter.SupportedMediaTypes;
if (supportedMediaTypes.Any(it => it.MediaType.IndexOf("application/xml", StringComparison.InvariantCultureIgnoreCase) >= 0) ==false)
{
supportedMediaTypes.Insert(0,new MediaTypeHeaderValue("application/xml"));
}
I normally just remove the XmlFormatter altogether.
// Remove the XML formatter
config.Formatters.Remove(config.Formatters.XmlFormatter);
Add the line above in your WebApiConfig class...
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
// Remove the XML formatter
config.Formatters.Remove(config.Formatters.XmlFormatter);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}

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

Storage for DataMappers in ASP.NET WebApplication

In Martin Fowler's "Patterns of Enterprise Application Architecture"
is described approach for organizing DAL like a set of mappers for entities. Each has it's own IdentityMap storing specific entity.
for example in my ASP.NET WebApplication:
//AbstractMapper - superclass for all mappers in DAL
public abstract class AbstractMapper
{
private readonly string _connectionString;
protected string ConnectionString
{
get { return _connectionString; }
}
private readonly DbProviderFactory _dbFactory;
protected DbProviderFactory DBFactory
{
get { return _dbFactory; }
}
#region LoadedObjects (IdentityMap)
protected Hashtable LoadedObjects = new Hashtable();
public void RegisterObject(long id, DomainObject obj)
{
LoadedObjects[id] = obj;
}
public void UnregisterObject(long id)
{
LoadedObjects.Remove(id);
}
#endregion
public AbstractMapper(string connectionString, DbProviderFactory dbFactory)
{
_connectionString = connectionString;
_dbFactory = dbFactory;
}
protected virtual string DBTable
{
get
{
throw new NotImplementedException("database table is not defined in class " + this.GetType());
}
}
protected virtual T Find<T>(long id, IDbTransaction tr = null) where T : DomainObject
{
if (id == 0)
return null;
T result = (T)LoadedObjects[id];
if (result != null)
return result;
IDbConnection cn = GetConnection(tr);
IDbCommand cmd = CreateCommand(GetFindStatement(id), cn, tr);
IDataReader rs = null;
try
{
OpenConnection(cn, tr);
rs = cmd.ExecuteReader(CommandBehavior.SingleRow);
result = (rs.Read()) ? Load<T>(rs) : null;
}
catch (DbException ex)
{
throw new DALException("Error while loading an object by id in class " + this.GetType(), ex);
}
finally
{
CleanUpDBResources(cmd, cn, tr, rs);
}
return result;
}
protected virtual T Load<T>(IDataReader rs) where T : DomainObject
{
long id = GetReaderLong(rs["ID"]);
T result = (T)LoadedObjects[id];
if (result != null)
return result;
result = (T)DoLoad(id, rs);
RegisterObject(id, result);
return result;
}
// another CRUD here ...
}
// Specific Mapper for entity Account
public class AccountMapper : AbstractMapper
{
internal override string DBTable
{
get { return "Account"; }
}
public AccountMapper(string connectionString, DbProviderFactory dbFactory) : base(connectionString, dbFactory) { }
public Account Find(long id)
{
return Find<Account>(id);
}
public override DomainObject DoLoad(long id, IDataReader rs)
{
Account account = new Account(id);
account.Name = GetReaderString(rs["Name"]);
account.Value = GetReaderDecimal(rs["Value"]);
account.CurrencyID = GetReaderLong(rs["CurrencyID"]);
return account;
}
// ...
}
The question is: where to store these mappers? How system services (entities) should call mappers?
I decided to create MapperRegistry containing all mappers. So services can call mappers like:
public class AccountService : DomainService
{
public static Account FindAccount(long accountID)
{
if (accountID > 0)
return MapperRegistry.AccountMapper.Find(accountID);
return null;
}
...
}
But where can I store MapperRegistry instance? I see following variants, but don't like any of them:
MapperRegistry is global for application (Singleton)
Not applicable because of necessity of synchronization in multi-thread ASP.NET application (at least Martin says that only mad can choose this variant)
MapperRegistry per Session
Seems not so good too. All ORMs (NHibernate, LINQ to SQL, EntityFramework) masters advise to use DataContext (NHibernateSession, ObjectContext) per Request and not to store context in Session.
Also in my WebApp almost all requests are AJAX-requests to EntityController.asmx (with attribute ScriptService) returning JSON. And session is not allowed.
MapperRegistry per Request
There are a lot of separate AJAX calls. In this case life cycle of MapperRegistry will be too small. So the data almost always will be retrieved from database, as a result - low performance.
Dear Experts, please help me with architectural solution.

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