Reputation: 660
After struggling with this issue for almost 2 days, I decided to come here and ask this question - I will do my best to be as clear as I can.
First of all, what am I trying to do is pretty simple: implement a custom IValueProvider that decrypts the parameters of an action inside an ASP.NET MVC Controller - this part is done and it's working perfectly; my issue starts on the moment when I have a List among the action parameters because I'm not able to exact the values of this list from the request context.
Let's take it step by step.
First of all, I have an attribute which decorates the required actions:
public class EncryptedQueryStringValuesAttribute : FilterAttribute, IAuthorizationFilter
{
/// <summary>
/// Called when authorization is required.
/// </summary>
/// <param name="filterContext">The filter context.</param>
public void OnAuthorization(AuthorizationContext filterContext)
{
filterContext.Controller.ValueProvider = new EncryptedQueryStringValuesProvider(
filterContext.RequestContext, filterContext.ActionDescriptor);
}
}
Next, I have the value provider constructor where I'm initializing what I need:
public EncryptedQueryStringValuesProvider(RequestContext requestContext, ActionDescriptor actionDescriptor)
{
this.requestContext = requestContext;
this.actionDescriptor = actionDescriptor;
this.prefixContainer =
new Lazy<PrefixContainer>(
() => new PrefixContainer(this.requestContext.HttpContext.Request.Params.AllKeys), true);
}
As a side note, the prefixContainer it really works like a charm - if I'm checking if a prefix is there in the container, it's working perfectly regardless of the prefix type (meaning it doesn't matter if the parameter is a collection or a simple parameter).
The checking of the existence of a parameter inside the value provider is done like this:
public bool ContainsPrefix(string prefix)
{
return this.prefixContainer.Value.ContainsPrefix(prefix);
}
As I've said - this gives me correct values.
Now, comes the ugly part - the GetValue
method:
public ValueProviderResult GetValue(string key)
{
string[] rawValue = this.requestContext.HttpContext.Request.Params.GetValues(key);
string attemptedValue = this.requestContext.HttpContext.Request.Params[key];
return new ValueProviderResult(rawValue, attemptedValue, CultureInfo.CurrentCulture);
}
The rawValue
as is extracted right now is null and is logical to be null because there is no key with that name in my request - all that I have there is just a collection like this:
[25]: "SupplierInvoices[x].PaymentToInvoiceExchangeRate"
[26]: "SupplierInvoices[x].AmountToPayOnGivenInvoice"
[27]: "SupplierInvoices[x].OpenInvoiceId"
[28]: "SupplierInvoices[x].OpenInvoiceTimestamp"
On the other hand, I'm fully aware that I have to use the this.prefixContainer.Value.GetKeysFromPrefix(prefix);
construction to obtain all the keys that are in my request and based on that, to somehow join these keys and gave them back to the ValueProviderResult, BUT HAVE NO IDEA HOW! :-)
Please, there is someone who can explain how these values can be joined in order to be passed back to the ValueProviderResult to be correctly interpreted?
THANK YOU!
Upvotes: 2
Views: 946
Reputation: 660
After fighting with this issue for the last weeks, I decided that I should change the approach and try to not re-invent the wheel. First of all, I should drop everything what I have related to the collection processing and all these things - so everything what is about this.prefixContainer
should go - if I cannot find the simple key in the HTTPContext, I should not process it and leave it to the rest of the providers.
Here comes another issue - how can I "tell" my value provider to "let it go" about a key??? Initially I thought that if I return false in the ContainsPrefix
method, the processing will switch to the next IValueProvider
in the queue - this is not the case - both the ContainsPrefix
method should return null and more then this, the GetValue
method should return null and only under these circumstances, the processing will move to the next IValueProvider
.
OK - now that I have all these things in place, how can I use my value provider without a ValueProviderFactory
registered globally because, as I've said, the processing takes place only for the action "signed" with a given attribute. The answer is like this - instead of instantiating the custom ValueProvider
in the Controller
using the attribute code like this:
filterContext.Controller.ValueProvider = new EncryptedQueryStringValuesProvider(filterContext.RequestContext, filterContext.ActionDescriptor);
I have to add my value provider to the top of the list:
ValueProviderCollection valueProviderCollection = filterContext.Controller.ValueProvider as ValueProviderCollection;
if (valueProviderCollection == null)
throw new NullReferenceException("filterContext.Controller.ValueProvider as ValueProviderCollection");
valueProviderCollection.Insert(0, new EncryptedQueryStringValuesProvider(filterContext.RequestContext, filterContext.ActionDescriptor));
I hope that this will help someone. :-)
Upvotes: 1