I'm having trouble understanding some web part communication code, and how to make it work as needed. I'm fairly new to web parts, so I figure I'm over complicating it.
I have
public interface IFormTypeRID
{
int FormTypeRID { get; }
}
For this page, I have a ListControl on the left which is a web part. When you select something with this list control, it posts back and sends this interface to the MainForm web part So I have this on the receiving end:
private IFormTypeRID theProvider;
[ConnectionConsumer("FormTypeRID Consumer", "FormTypeRIDConsumer")]
public void InitializeProvider(IFormTypeRID provider)
{
theProvider = provider;
FormTypeRID = theProvider.FormTypeRID;
}
Now, my problem is that InitializeProvider happens after Page_Load.
This means that when a post back happens from inside the MainForm webpart, the FormTypeRID is never sent in. Well, thats easy just store it in view state or a hidden field.
But theres the problem.
How do I act on FormTypeRID being set if it's after PageLoad. Currently, I just have a setter method that calls the appropriate functions. This is troublesome however because the code in the setter must be executed twice. This is because FormTypeRID must be set in Page_Load because it is unknown whether our provider will be giving us our FormTypeRID or not(because we do not know if the post back happened because of the other webpart, or because of the FormMain)
I know I explained that horribly. But basically, how do you tell if a postback happened(and therefore a page_load) from one webpart(the provider) or another?(the consumer)
The answer to all of this is non-trivial.
I ended up writing my own "webpart communication" which actually ended up being a lot cleaner than ASP.Net's and it will work during Init and Load and so on.
Related
I'm currently working on a large project involving Sitecore CMS (7.2). For viewmodel validation we are using FluentValidations. Because of the combination of Sitecore and FluentValidations I seem to be running in some kind of technical deadlock. I sort-of found a solution myself, but I'm not sure whether this is the right approach or not. Here's the problem:
Situation
There is a Sitecore component which contains a HTML form. Via the modelbinder each value of this form is binded to it's corresponding field in the (complex) viewmodel. This is standard .NET MVC approach.
However, some values in the viewmodel are NOT part of the form. For instance, a date at which the mutation will be applied is calculated by the application. The user can only see this date-value as plain text, and thus can not edit it. It's still part of the viewmodel though. To make sure this value is being posted back to the model in code, one would normally use a hidden field. But if I use a hidden field, it means that users are able to spoof that date and because some validations depend on this value, they are able to spoof the entire validity of the form.
Moreover, in the same viewmodel I have a list of complex objects that I can't simply put in a hidden field (or I should serialize it to JSON, which I don't want).
The conclusion is that I need to store this data somewhere else. Somewhere the user can't spoof it, but I'm still able to validate user input with FluentValidations. I therefore decided to put the entire viewmodel in the users Session, and delete it directly after a succesful mutation.
Problem
By using session data I run into problems. Let's first see these steps:
(GET) Viewmodel is created. Calculated date is set and list of complex types is retrieved once from a (slow) webservice.
Entire viewmodel is stored as session data.
Form is shown to the user, who fills the form. Some data is only shown as readonly, like the date and list of complex types.
User submits form, FluentValidations kicks in to validate the data (POST).
That's where I run into problems. The validation done by FluentValidations kicks in before it reaches the POST controller method. That's exactly the way we want it, because that means validation errors are automatically added to the ModelState. However, because of security reasons I don't want to add this data as hidden fields to the cshtml file, which means they are empty at the time FluentValidations is going to validate the form.
This is creating problems because some of the form validations rely on the missing data. What I basically want is to merge the viewmodel that is stored in the session with the viewmodel that was posted to the controller method. But I have to do that before FluentValidations is going to do it's work.
My current solution
Gladly, I learned about FluentValidation's IValidatorInterceptor: an interface that can be used to 'do stuff' before or after the validations process kicks in. I used the BeforeMvcValidation method to do my merging process. The code is as follows:
public ValidationContext BeforeMvcValidation(ControllerContext controllerContext, ValidationContext validationContext)
{
if (controllerContext.HttpContext.Session == null)
return validationContext;
var sessionData = controllerContext.HttpContext.Session["some_identifier"];
if (sessionData == null)
return validationContext;
var mergedObjectToValidate = Utils.MergeViewModelData(sessionData, validationContext.InstanceToValidate);
// Unfortunately, we have to do this..
var privateSetterProperty = validationContext.GetType().GetProperty(ValidationContextInstancePropertyName);
if (privateSetterProperty == null)
return validationContext;
privateSetterProperty.SetValue(validationContext, mergedObjectToValidate);
return validationContext;
}
Basically this interceptor method allows me to do my merging-process before validation. So I thought I had the solution here, but as you can see I am using reflection to set a property. That is because the property InstanceToValidate in the ValidationContext object has a private setter. I simply can not set it without using reflection. Which is, obviously, a bit dirty.
It does work exactly as I want though! :) I do not need any hidden fields that can be spoofed (which is horrible for straight-trough-processing) and I can still use FluentValidations exactly as I always did before. Also, the MVC modelbinding-process is left untouched, which I prefer.
The actual question
So the above solution works exactly as you want so what are your questions?! Well, simple:
I'm using reflection to set a private property in a 3rd party library (FluentValidations). The obvious answer is: don't go that way. But in this case it works flawlessly. If the InstanceToValidate-property had a public setter, I wouldn't even be posting this question at all: I would feel like I nailed it. But unfortunately it is private, so are there any real reasons why I shouldn't do this, maybe someone being an expert in FluentValidations behaviour?
Let's say there is a genuine reason why I shouldn't go this way; is there another approach which has the same effect? Can I hook in even earlier, so before FluentValidations kicks in, perhaps some kind of 'hook' just after the MVC model-binding process but before validation kicks in?
Is this entire approach simply wrong and should I tackle it in a completely different way?
Thanks!
I've got a Page, a GridView using an ObjectDataSource with a SelectMethod and a DropDownList. The SelectMethod, among other things, gets a string-array containing several IDs (to filter the Data) - but I also need it as DataSource for the DropDownList.
Alas, I cannot DataBind the DropDownList inside the SelectMethod since it's null.
An Idea would be to bind this string[] to a Session-Variable, but then I'd have to either re-set it upon every Page_Load or remove it from Session on every other page if I want it to update in case something on the Database changed.
What I'm looking for is some kind of variable that is available both in Page_Load and my ObjectDataSources SelectMethod, but that removes itself upon leaving the page (i.e. navigating to any other page on my Web-Application (preferably without having to call a method on EVERY other Page).
I hope you could understand my problem.
Thanks,
Dennis
As I understand the need to fetch the string array arises from the performance hit that a separate roundtrip will cause. To work around this you may create a separate object to feed your object data source. This object will have two methods one for getting the string array and another for getting the data for the grid (i.e. the select method)
You may then put an object like this in your page and fetch the data in it in a lazy manner. If the object makes a call for any of the data it stores the other part in a field. You can then use the ObjectDataSource ObjectCreating event to pass this object on your page to the ObjectDataSource
protected void odsSomething_ObjectCreating(object sender, ObjectDataSourceEventArgs e)
{
e.ObjectInstance = YourInsntanceAlreadyInThePage;
}
This way you will avoid the roundtrip.
Also consider making two web service calls at the same time using the asynchronous client calls so that you can make both calls for the same time. If this is viable depends on the flow of your logic.
What I'm looking for is some kind of variable that is available both in Page_Load and my ObjectDataSource's SelectMethod, but that removes itself upon leaving the page (i.e. navigating to any other page on my Web-Application (preferably without having to call a method on EVERY other Page).
In a similar situation, I've used the Items property of the current HttpContext. It's an IDictionary (non-generic), so can hold arbitrary objects keyed by arbitrary objects, and its lifetime is precisely the duration of the current request, so will go away as soon as the request is ended. To use:
// Where you first get the data
HttpContext.Current.Items["SomeKey"] = new [] { "string1", "string2" };
// Where you want to to use the data
var strings = (string[])HttpContext.Current.Items["SomeKey"];
Based on my last question, I have tried to separate the business logic from my controller completely.
This however has left a problem which I understand why, but not how to fix.... And, I do not understand why it is doing what is doing.
In my controller, I had the following:
public User GetCurrentUser()
{
User user = db.Users.SingleOrDefault(x => x.UserName == User.Identity.Name);
return user;
}
I now know about [NonAction] which fixes the security concern - however, I know this doesn't follow best practices of not having any non controller stuff in a controller. For this reason, I moved to a new class and modified it to the following:
public User GetCurrentUser(string name)
{
User user = db.Users.SingleOrDefault(x => x.UserName == name);
return user;
}
I have an edit method which before simply set various fields in the user object, then called db.SaveChanges(). This however is now causing issues - I believe it is due to calling the command on a db object that doesn't actually have the object loaded.
But, the part that I really do not understand is when I am redirected back to the home page and perform GetCurrentUser() again, I am presented with the edited details I changed... These are not stored in the database and it is only when I restart the application it goes back to the database results.
I am finding this very confusing! What is happening here and where are the object being stored?
And, how do I fix it? I have tried making the new Class's db function public and calling it's SaveChanges() method, but, this is resulting in the same problem - data that is not being saved to the database.
Anyway, quite frankly, I really liked calling it via just GetCurrentUser(), I wanted this due to the fact I wanted to change the way the user was loaded in the future - but, now that I have to call it via GetCurrentUser(User.Identity.Name), and make other modifications, I think it wouldn't be that much harder to just skip on the method and call the Lambda query directly instead... It just seems it will save a lot of trouble.
Based on the detail in your question, you need to make sure you attaching your Entity object e.g db.Users.Attach(updatedUser)
And then change its state
e.g db.ObjectStateManager.ChangeObjectState(updatedUser, EntityState.Modified)
Before you call db.SaveChanges()
The edit functions I've written for my MVC app usually have one more line of code before I call SaveChanges:
_db.ApplyCurrentValues(OriginalEntity.EntityKey.EntitySetName, NewEntity);
_db.SaveChanges();
Also, maybe I'm missing something but wouldn't this (below) be a simpler way to update the user information in the database?
Membership.UpdateUser();
As for the reason why the non-database data is still showing up for you, I think that is because when you call GetCurrentUser it caches information on the client side. I'm sure someone with more experience here can give a more detailed (or more correct answer) on that part.
Following my previous question as to whether ASP.net's default Page.IsPostBack implementation is secure (it's not; it can be faked... the HTTP verb doesn't even have to be POST!), I was thinking; surely there must be a better way to implement it? Can we come up with a Page.IsPostBack implementation which, when it is true, is almost guaranteed to indicate that the page is an actual ASP.net postback? This is important if one wants to do security checking only once (like whether some content is going to appear, based on the user's role(s)), and wants to do it only if we're NOT dealing with an ASP.net postback.
My first thoughts as to how to do this are to implement the checking code in a property, so I can write something like this inside Page_Load:
if (!_isPostBack)
{
// Do security check
if (userIsNotAuthorized)
{
btnViewReports.Visible = false;
btnEditDetails.Visible = false;
// etc.
}
}
Is there a way to securely implement _isPostBack? Perhaps storing something in the ViewState that would be hard or impossible to jerry-rig to fake a postback? A random string?
I had a project a couple of years ago where we had some penetration testing done on the code. They flagged up the fact that by default IsPostback doesn't check the http verb. To address this I created an abstract Page class with its' own implementation of IsPostback that shadows the default implmentation:
Public Class ProjectPage : System.Web.UI.Page
public new bool IsPostBack()
{
return (Page.IsPostBack && Request.HttpMethod.ToUpper() == "POST");
}
End Class
This allows you to do testing on the http verb, but you could easily extend the method to do other checks as well.
OK, here's what I think is the solution: Page.IsPostBack is already secure enough, as long as event validation is enabled. Let me explain my reasoning below and I'd be happy for anyone to add a comment if I've gotten something wrong.
In order for a spoof postback to be posted to ASP.net and trigger a control's OnClick event, with event validation enabled, the client has to send the __EVENTVALIDATION form field. This field contains a uniquely-generated string that basically tells ASP.net which controls a postback event for that page may have originated from. If you try to spoof a postback for a button which has had .Visibility = false set on it, you'll see an event validation error message. So, it looks like you can't directly spoof a click on a hidden control.
What about spoofing a postback of one of the existing buttons on the page that you has been rendered (ie. you do have permission to view/click on it)? Well, you can send the postback to the page, but you need to submit a valid __VIEWSTATE or you'll just get a 'state information invalid' error. In order to have a valid __VIEWSTATE, you already need to have loaded the page as a non-postback, right? That means that the security-checking code will have executed at least once, hiding the appropriate controls and recording that in the __VIEWSTATE. So, when you post the spoof postback, yes it will cause Page.IsPostBack to be true, but it doesn't matter because the submitted __VIEWSTATE will already have been generated on the previous non-postback page load to hide the content that you shouldn't have access to... so, you can spoof a postback, but only by passing a __VIEWSTATE that has been previously generated by a non-postback page load.
So, because of these facts, it should be safe to only put security-checking code inside a Page.IsPostBack == false block. This must always get run once before a valid postback can be submitted to the ASP.net server. Or am I missing something?
A cookie is a much better mechanism for your needs. The cookie is a token that could only have been generated by the server and vouches for the holder of the token certain claims such as having signed in recently and for having certain permissions and/or preferences. Some of these features are built into FormsAuthentication. You can implement your own cookie mechanism, but you should research secure cookie protocols because there are several non-obvious security considerations.
The benefit is that you don't have to go to the database on every request, you just trust it. This could also be a good strategy for weathering certain DoS attacks because you can tier your app such that a dedicated device in front of your app servers validates tokens too and throws out invalid requests.
If cookies aren't allowed, you can send the token as part of the url, as formsauth allows, or as a form field in your postback. But that's more work to manage then cookies, IMHO, once you've gone through the trouble of generating a proper token.
I have created custom MembershipUser, MembershipProvider and RolePrivoder classes. These all work and I am very happy with it, apart from one thing. I have an extra field in the "Users" table. I have an overridden method for CreateUser() that takes in the extra variable and puts it into the DB.
My issues is that I want to be able to have this called from the Create User Wizard control. I have customized the control to have a drop down to populate my extra field. I have used the following code to store that piece of info but I am at a loss of how I either use the profile or call my custom CreateUser Method:
// Create an empty Profile for the newly created user
ProfileCommon p = (ProfileCommon)ProfileCommon.Create(CreateUserWizard1.UserName, true);
// Populate some Profile properties off of the create user wizard
p.CurrentLevel = Int32.Parse(((DropDownList)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("clevel")).SelectedValue);
// Save profile - must be done since we explicitly created it
p.Save();
Thank you for any and all help
Jon Hawkins
I think your solution is the "easiest" you're going to get. You could create your own wizard and call the correct method, but that's a lot more work.
The only thing I could recommend is using the OnCreatedUser event instead.
reference: 4guysfromrolla
This is not the answer but I found a work around, would still like to know if someone could answer the question directly...
public void UpdateCurrentLvl_OnDeactivate(object sender, EventArgs e)
{
int level = Int32.Parse(((DropDownList)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("clevel")).SelectedValue);
MyMembershipUser myUser = (MyMembershipUser)Membership.GetUser(CreateUserWizard1.UserName);
myUser.CurrentLVL = level;
Membership.UpdateUser(myUser);
}
In my first CreateUserWizardStep if put the method above to fire on deactivate. As at this point it has inserted the user into the DB I can get the User out, cast to my MembershipUser class, set the variable and all the update method.
As I say this is a work around from the way I would liked to have solved it but it works.
Thanks
This is also incredibly hacky, but in the past when I've had to do similar things, I've just crammed the extra value into an unused parameter, like the password reset question or answer. Though, I'm not entirely sure how you'd manage this using the wizard.
It's ugly, but as long as it is explicitly documented (to be safe, I'd comment on the method itself as well as anywhere you reference it) it will work fine.
It's also a lot less work than creating your own wizard.