4

Considering a simple example of a webpart that adds items to a list based on a simple button click:

public partial class MyVisualWebPartUserControl : UserControl
{
    private int cont = 0;

    protected void MyButton_Click(object sender, EventArgs e)
    {
        using (SPSite site = new SPSite("http://localhost"))
        {
            using (SPWeb web = site.OpenWeb())
            {
                SPList list = web.Lists["MyList"];
                SPListItem item = list.Items.Add();
                item["Title"] = "Title " + cont++;
                //......
            }
        }
    }
}

The title of the element, on each click of a button, should be:

Title 0
Title 1
Title 2
Title 3
Title 4
etc

Off course this isn't the case and this is outputted:

Title 0
Title 0
Title 0
Title 0
Title 0
etc

What options do I have to be able to manage such state inside the webpart?

Can I make it "per user" (each user with his own counter)?

3 Answers 3

1

When dealing with counters of any kind I like to use different approach. Even there are lots of other possibilities I like to store counter values in separated list (counter list).

In this case counter list will store value pairs: userId, current count.

Here is code:

cont =  GetNextId(SPContext.Current.Web.CurrentUser.ID);

private int GetNextId(int userId)
{
    var listName = "MyCounter";
    var nextId = 1;
    var webUrl = SPContext.Current.Web.Url;
    //Lets run things elevated
    SPSecurity.RunWithElevatedPrivileges(delegate
    {
        using (var site = new SPSite(webUrl))
        {
            using (var web = site.OpenWeb())
            {
                // Important part
                web.AllowUnsafeUpdates = true;
                var counterList = web.Lists.TryGetList(listName);
                if (counterList == null)
                {
                    // List doesn't exist
                    var listGuid = web.Lists.Add(listName, "", 
                                     SPListTemplateType.GenericList);
                    counterList = web.Lists[listGuid];
                    counterList.Fields.Add("counter", SPFieldType.Number, 
                                                        false, false, null);
                    // You can hide list
                    counterList.Hidden = true;
                    counterList.Update();
                    // You can even strip all permissions 
                    // to be sure noone can alter it
                    counterList.BreakRoleInheritance(false);
                }

                // Find user Title == UserId
                var query = new SPQuery();
                query.Query = string.Format(@"<Where><Eq><FieldRef Name=""Title""/>
                             <Value Type=""Text"">{0}</Value></Eq></Where>", userId);
                var items = counterList.GetItems(query);

                if (items.Count == 0)
                {
                    // No user. Let's create one
                    var counterNew = counterList.Items.Add();
                    counterNew["Title"] = userId;
                    counterNew["counter"] = 2;
                    counterNew.Update();
                }
                else
                {
                    // User found. Read current counter value
                    // and increment it by one
                    var result = Convert.ToInt32(items[0]["counter"].ToString());
                    var item = counterList.GetItemById(items[0].ID);
                    item["counter"] = result + 1;
                    item.Update();
                    nextId = result;
                }
                web.AllowUnsafeUpdates = false;
            }
        }
    });

    return nextId;
}

Is it overkill? Yes it is, but from past experience I can say it is bulletproof solution... almost.

4
  • "...from past experience...". What are some gotchas or issues you can go into if using for example a (standard) solution like Sébastien Levert suggested?
    – JohnDoDo
    Commented Oct 6, 2011 at 7:16
  • eg. performance: Let's say you have list with 5000+ items. Running query against it is much more slower then keeping counter(s) in separate list. Next: Possibility of receiving two identical ids is lower in my example. Don't get me wrong, there is nothing wrong with @SébastienLevert suggestion expecially for simple scenarios. Commented Oct 6, 2011 at 7:29
  • I'm going for this solution eventually because it's global (just found out that if I add the webpart to more pages the counter starts at zero for each, i.e. the state is related to the page). Also, if I access the counter from a child user control I must go up in the parent webpart for the counter and also call its SetPersonalizationDirty method which isn't easily accessible from (non inheritance) child controls. Thanks!
    – JohnDoDo
    Commented Oct 6, 2011 at 9:47
  • 1
    If you are using more then one web part you can store default counter list name in web part custom property and then each web part can have unique counter list. Commented Oct 6, 2011 at 9:51
2

You can use the WebBrowsable properties of a property in a web part. Like the following :

[WebBrowsable(true),
WebDisplayName("Items count"),
WebDescription("Items count"),
Personalizable(PersonalizationScope.User)]
public int Count { get; set; }

EDIT :

In the case (see the comments below) where you don't want to check-in/check-out the complete page, you could use some logic to query the list based on a simple CAML query, get the items count by user in the list, and adding +1 to that count ?

5
  • Then you can use this.SetPersonalizationDirty(); to save the property's value after you update it.
    – Kit Menke
    Commented Oct 5, 2011 at 14:50
  • Actually... I'm not sure he wants to do this because if the page requires check in/out then you'd have to edit the entire page?
    – Kit Menke
    Commented Oct 5, 2011 at 14:53
  • That makes lots of sense... It clearly depends on his needs... But that element could affect the solution. Commented Oct 5, 2011 at 15:11
  • You need to be careful if using CAML query and items count like Sébastien suggested. If you for example delete one item in list (not last one you added) you will have duplicate numbers. You can avoid it by storing count number in separate field in each item. Then you can query looking for biggest one and add +1 to it. This approach however has some other issues. Commented Oct 5, 2011 at 19:57
  • +1 for letting me know about those attributes. But I eventually accepted @Vedran Rasol answer for the reasons stated in my comment to his answer. Thanks!
    – JohnDoDo
    Commented Oct 6, 2011 at 9:50
0

Not sure how well this'll work, but could you save the value of your cont variable in the ViewState bag each time you increment it?

4
  • I think he wants it to be available across multiple visits to the webpart. The viewstate will only be available during a single visit of the page. Am I wrong on that one ? Commented Oct 5, 2011 at 16:00
  • In that case, I'm certain just writing to the Web Part Property would do it. Properties aren't persisted to the List Item of the Page, so it shouldn't need checking out.
    – James Love
    Commented Oct 5, 2011 at 16:13
  • Hmm.. I was trying this because I was curious and I keep getting the error The file is not checked out. You must first check out this document before making changes. when I use this.SetPersonalizationDirty(); in my web part. Edit: Looks like this is because it is a publishing page rnowik.com/…
    – Kit Menke
    Commented Oct 5, 2011 at 16:21
  • Gotta love the Publishing Infrastructure for it's "quirks".
    – James Love
    Commented Oct 5, 2011 at 16:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.