AllowUnsafeUpdates 的最佳模式

发布于 2024-07-06 17:13:36 字数 1145 浏览 11 评论 0原文

到目前为止,在我的研究中,我发现在 GET 请求操作上设置 AllowUnsafeUpdates 以避免跨站点脚本编写是不明智的。 但是,如果需要允许这样做,处理这种情况以减轻风险的正确方法是什么?

如果您绝对需要允许 Web 或站点更新 GET 请求,那么这是我对可靠模式的最佳初步猜测。

最佳实践?

protected override void OnLoad(System.EventArgs e)
{
    if(Request.HttpMethod == "POST")
    {
        SPUtility.ValidateFormDigest();
        // will automatically set AllowSafeUpdates to true
    }

    // If not a POST then AllowUnsafeUpdates should be used only
    // at the point of update and reset immediately after finished

    // NOTE: Is this true? How is cross-site scripting used on GET
    // and what mitigates the vulnerability?
}

// Point of item update

    using(SPSite site = new SPSite(SPContext.Current.Site.Url, SPContext.Current.Site.SystemAccount.UserToken))
    {
        using (SPWeb web = site.RootWeb)
        {
            bool allowUpdates = web.AllowUnsafeUpdates; //store original value
            web.AllowUnsafeUpdates = true;

            //... Do something and call Update() ...

            web.AllowUnsafeUpdates = allowUpdates; //restore original value

        }
    }

欢迎提供有关最佳模式的反馈。

So far, in my research I have seen that it is unwise to set AllowUnsafeUpdates on GET request operation to avoid cross site scripting. But, if it is required to allow this, what is the proper way to handle the situation to mitigate any exposure?

Here is my best first guess on a reliable pattern if you absolutely need to allow web or site updates on a GET request.

Best Practice?

protected override void OnLoad(System.EventArgs e)
{
    if(Request.HttpMethod == "POST")
    {
        SPUtility.ValidateFormDigest();
        // will automatically set AllowSafeUpdates to true
    }

    // If not a POST then AllowUnsafeUpdates should be used only
    // at the point of update and reset immediately after finished

    // NOTE: Is this true? How is cross-site scripting used on GET
    // and what mitigates the vulnerability?
}

// Point of item update

    using(SPSite site = new SPSite(SPContext.Current.Site.Url, SPContext.Current.Site.SystemAccount.UserToken))
    {
        using (SPWeb web = site.RootWeb)
        {
            bool allowUpdates = web.AllowUnsafeUpdates; //store original value
            web.AllowUnsafeUpdates = true;

            //... Do something and call Update() ...

            web.AllowUnsafeUpdates = allowUpdates; //restore original value

        }
    }

Feedback on the best pattern is appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

南风几经秋 2024-07-13 17:13:36

如果您正在执行任何修改某些内容的操作,那么任何可以说服用户单击链接的人都可以执行该操作。 例如,假设您有一个对页面的 GET 请求,该页面允许用户将管理员添加到站点,并且用户单击指向执行 Response.Redirect("http://yourserver/_layouts/admin.aspx?operation=addAdministrator&username=attackerNameHere")。

虽然通常情况下 POST 并不能提供太多的保护(没有什么可以阻止某人使用

),但 SharePoint 有一个概念表单摘要,其中包含有关生成回发的先前请求的信息(包括用户名)。 这显着减少了此类攻击的足迹。

GET 上的 AllowUnsafeUpdates 不存在安全问题的唯一情况是您不接受用户的输入。 例如,如果您有一个 Web 部件也记录对列表的访问,则不会暴露任何安全漏洞。

编辑:如果您要使用AllowUnsafeUpdates,则无需将其重置为之前的值。 它不会被持久化。 这只是您在执行 GET 更新(或其他情况)之前需要在 SPWeb 对象上设置的内容

If you're performing any operations which modify something, then anyone that can convince the user to click on a link can perform that operation. For instance, let's assume that you have a GET request to a page which lets the user add an administrator to a site, and the user clicks a link to a page which does a Response.Redirect("http://yourserver/_layouts/admin.aspx?operation=addAdministrator&username=attackerNameHere").

While normally a POST does not offer much protection against this (nothing will stop someone from having a <form method="post" action="http://yourserver/_layouts/admin.aspx">), SharePoint has a concept of form digests, which contain information about the previous request that is generating the post back (including the user's name). This reduces the footprint for this kind of attack significantly.

The only time that it is not a security issue to AllowUnsafeUpdates on a GET is if you're not taking input from the user. For instance, if you have a web part which also logs visits to a list, then there's no security vulnerability exposed.

Edit: If you are going to use AllowUnsafeUpdates, there's no need to reset it to its previous value. It does not get persisted. It's just something you need to set on an SPWeb object before performing updates from a GET (or other cases)

勿忘初心 2024-07-13 17:13:36

我会稍微修改 Trent 的委托以接受网络更新:

public static void DoUnsafeUpdate(this SPWeb web, Action<SPWeb> action)
{
    try
    {
        web.AllowUnsafeUpdates = true;
        action(web);
    }
    finally
    {
        web.AllowUnsafeUpdates = false;
    }
}

然后扩展 HttpContext 来封装表单摘要的验证,并可以选择使用 此处描述的技术

public static void DoUnsafeUpdate(this HttpContext context, Action<SPWeb> action, bool elevated)
{
    SPWeb web = SPControl.GetContextWeb(context);
    if (!context.Request.HttpMethod.Equals("POST", StringComparison.Ordinal)
        || web.ValidateFormDigest())
        throw new SPException("Error validating postback digest");

    if (elevated)
        web.RunAsSystem(w => w.DoUnsafeUpdate(action));
    else
        web.DoUnsafeUpdate(action);
}

用法:

protected override void OnLoad(System.EventArgs e)
{
    Context.DoUnsafeUpdate(web =>
    {
        // Update elevated web
    }, true);
}

I would slightly modify Trent's delegate to accept the web to update:

public static void DoUnsafeUpdate(this SPWeb web, Action<SPWeb> action)
{
    try
    {
        web.AllowUnsafeUpdates = true;
        action(web);
    }
    finally
    {
        web.AllowUnsafeUpdates = false;
    }
}

And then extend HttpContext to encapsulate verification of the form digest, with an option to elevate using the technique described here:

public static void DoUnsafeUpdate(this HttpContext context, Action<SPWeb> action, bool elevated)
{
    SPWeb web = SPControl.GetContextWeb(context);
    if (!context.Request.HttpMethod.Equals("POST", StringComparison.Ordinal)
        || web.ValidateFormDigest())
        throw new SPException("Error validating postback digest");

    if (elevated)
        web.RunAsSystem(w => w.DoUnsafeUpdate(action));
    else
        web.DoUnsafeUpdate(action);
}

Usage:

protected override void OnLoad(System.EventArgs e)
{
    Context.DoUnsafeUpdate(web =>
    {
        // Update elevated web
    }, true);
}
农村范ル 2024-07-13 17:13:36

另一种干净的实现方法是使用扩展方法和匿名委托的组合,如下所示:

public static void DoUnsafeUpdate(this SPWeb web, Action action)
{
    bool allowUnsafeUpdates = web.AllowUnsafeUpdates;
    web.AllowUnsafeUpdates = true;
    action();
    web.AllowUnsafeUpdates = allowUnsafeUpdates;
}

使用上述扩展方法,您可以执行“不安全更新”操作,如下所示:

var web = SPContext.Current.Web;
web.DoUnsafeUpdate(delegate()
{
    // Put your "unsafe update" code here
});

Another clean way to implement would be to use a combination of extension methods and anonymous delegates as such:

public static void DoUnsafeUpdate(this SPWeb web, Action action)
{
    bool allowUnsafeUpdates = web.AllowUnsafeUpdates;
    web.AllowUnsafeUpdates = true;
    action();
    web.AllowUnsafeUpdates = allowUnsafeUpdates;
}

Using the above extension method, you can then perform your "unsafe update" action as follows:

var web = SPContext.Current.Web;
web.DoUnsafeUpdate(delegate()
{
    // Put your "unsafe update" code here
});
冷…雨湿花 2024-07-13 17:13:36

对于AllowUnsafeUpdates,我遵循以下流程:

if( HttpContext.Current is null )
{
  Do nothing, no need to set AllowUnsafeUpdates to true nor
  to call ValidateFormDigest() because update will be carried out
}
else // HttpContext.Current is NOT null
{
  if( SPContext.Current is null )
  {
    Need to set AllowUnsafeUpdates to true
  }
  else // SPContext.Current is NOT null
  {
    Call ValidateFormDigest()
  }
}

For AllowUnsafeUpdates, I follow this process:

if( HttpContext.Current is null )
{
  Do nothing, no need to set AllowUnsafeUpdates to true nor
  to call ValidateFormDigest() because update will be carried out
}
else // HttpContext.Current is NOT null
{
  if( SPContext.Current is null )
  {
    Need to set AllowUnsafeUpdates to true
  }
  else // SPContext.Current is NOT null
  {
    Call ValidateFormDigest()
  }
}
寄居者 2024-07-13 17:13:36

不太确定是否值得记住允许不安全更新的先前值。

我希望将调用包装在尽可能少的代码周围,这样就不会发生对其的嵌套调用。

然后你可以将其设置为 false。

Not so sure it is worth remembering the previous value of allow unsafe updates.

I would want to wrap the call around the minimum possible amount of code, so that nested calls to it would not occur.

Then you can just turn it to false afterwards.

﹏半生如梦愿梦如真 2024-07-13 17:13:36

我使用包装类来处理 SPWeb 对象的大多数操作。 这帮助我记住关闭网络,并且缓解了不安全更新设置的问题。 它有点臃肿,因为我已经修补了新的构造函数和成员。 但话又说回来; SPWeb 类也是如此。

用法:

using (WebWrapper wrapper = new WebWrapper("http://localhost"))
            {
                wrapper.AllowUnsafeUpdates();

                //Do work on wrapper.
            }

类定义:

using System;
using System.Collections.Specialized;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.Serialization;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace Skaar.SharePoint.Customization
{
    /// <summary>
    /// A wrapper for a <see cref="SPWeb"/> object.
    /// <remarks>Closes web object on Dispose if applicable.</remarks>
    /// </summary>
    [Serializable]
    [DebuggerDisplay("{Uri} Unsafe:{AllowUnsafeUpdatesSetting} Update:{UpdatePending}")]
    public sealed class WebWrapper : IDisposable, IDeserializationCallback, IEquatable<WebWrapper>
    {
        [NonSerialized] private bool unsafeUpdatesSetting;

        [NonSerialized] private SPWeb web;

        /// <summary>
        /// Determines if the inner web object should be closed.
        /// </summary>
        [NonSerialized] private bool webShouldBeClosed;

        /// <summary>
        /// This value is used in serialization to restore <see cref="Web"/>.
        /// </summary>
        private string webUrl;

        /// <summary>
        /// Creates a new wrapper object.
        /// </summary>
        /// <param name="web">A web that should be closed/disposed when done.</param>
        public WebWrapper(SPWeb web) : this(web, true)
        {
        }

        /// <summary>
        /// Creates a new wrapper object.
        /// </summary>
        /// <param name="web">An inner web object</param>
        /// <param name="webShouldBeClosed">If true, the web object is closed in the <see cref="Dispose()"/> method.</param>
        public WebWrapper(SPWeb web, bool webShouldBeClosed)
        {
            setWeb(web, webShouldBeClosed);
        }

        /// <summary>
        /// Creates a new wrapper object.
        /// </summary>
        /// <param name="webAddress">The address to a web.</param>
        public WebWrapper(Uri webAddress)
        {
            using (SPSite site = new SPSite(webAddress.ToString()))
            {
                string relativeUrl = renderWebRootRelativeUrl(webAddress);
                if (relativeUrl == null)
                {
                    setWeb(site.OpenWeb(), true);
                }
                else
                {
                    setWeb(site.OpenWeb(relativeUrl), true);
                }
            }
        }

        private string renderWebRootRelativeUrl(Uri address)
        {
            for (int i = 0; i < address.Segments.Length; i++)
            {
                string segment = address.Segments[i];
                if (string.Equals(segment, "_layouts/"))
                {
                    string newUrl=string.Join(null, address.Segments, 0, i).Trim('/');
                    return newUrl;
                }
            }
            return null;
        }

        /// <summary>
        /// If true, <see cref="SPWeb.Update"/> will be called in <see cref="Dispose()"/>.
        /// </summary>
        public bool UpdatePending { get; private set; }

        /// <summary>
        /// The setting of the inner web (<see cref="SPWeb.AllowUnsafeUpdates"/>)
        /// </summary>
        public bool AllowUnsafeUpdatesSetting
        {
            get { return Web.AllowUnsafeUpdates; }
        }

        /// <summary>
        /// The inner object.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Exception is thrown if <see cref="IsDisposed"/> is true.</exception>
        public SPWeb Web
        {
            get
            {
                if(IsDisposed)
                {
                    throw new ObjectDisposedException("Web wrapper is disposed.");
                }
                return web;
            }
        }

        /// <summary>
        /// The address of the <see cref="Web"/> wrapped as a <see cref="Uri"/> object.
        /// </summary>
        public Uri Uri
        {
            get { return new Uri(Web.Url); }
        }

        /// <summary>
        /// The address of the <see cref="Web"/> wrapped as a <see cref="Uri"/> object.
        /// </summary>
        public Uri GetUri(SPUrlZone zone)
        {
            return Site.WebApplication.GetResponseUri(zone, Uri.AbsolutePath);
        }

        /// <summary>
        /// Creates a wrapper around the context web.
        /// <remarks>The web will not be closed when wrapper is disposed. Returns null if context is unavailable.</remarks>
        /// </summary>
        public static WebWrapper Context
        {
            get
            {
                return SPContext.Current==null?null:new WebWrapper(SPContext.Current.Web, false);
            }
        }

        /// <summary>
        /// This is a static property wrapping of
        /// the <see cref="CloneOf(SPWeb)"/> method, using
        /// the <see cref="SPContext"/> current web as
        /// parameter.
        /// <remarks>Returns null if context is unavailable.</remarks>
        /// </summary>
        public static WebWrapper CloneOfContext
        {
            get
            {
                if (SPContext.Current != null)
                {
                    SPWeb contextWeb = SPContext.Current.Web;
                    return CloneOf(contextWeb);
                }
                return null;
            }
        }

        /// <summary>
        /// Returns the <see cref="SPWeb.Exists"/> property of the <see cref="Web"/> object.
        /// </summary>
        public bool Exists
        {
            get { return Web != null && Web.Exists; }
        }

        /// <summary>
        /// Gets the <see cref="SPSite"/> object of <see cref="Web"/>.
        /// </summary>
        /// <remarks>This object should not be closed by user code.</remarks>
        public SPSite Site
        {
            get { return web.Site; }
        }

        /// <summary>
        /// Gets the owner defined in <see cref="SPSite.Owner"/>.
        /// </summary>
        public SPUser Owner
        {
            get
            {
                return Site.Owner;
            }
        }

        /// <summary>
        /// Returns a context of the inner <see cref="Web"/>.
        /// </summary>
        public SPContext ContextOfWeb
        {
            get { return SPContext.GetContext(web); }
        }

        /// <summary>
        /// Gets the language of <see cref="Web"/>.
        /// </summary>
        public CultureInfo Locale
        {
            get { return Web.Locale; }
        }

        /// <summary>
        /// Gets the language of the root web.
        /// </summary>
        public CultureInfo LocaleOfRoot
        {
            get
            {
                using (WebWrapper root = Root)
                {
                    return root.Locale;
                }
            }
        }

        /// <summary>
        /// Returns a new <see cref="WebWrapper"/> wrapping the root <see cref="SPWeb"/> of this.
        /// </summary>
        public WebWrapper Root
        {
            get
            {
                if (webShouldBeClosed)
                    using (SPSite site = Site)
                    {
                        return new WebWrapper(site.RootWeb);
                    }
                return new WebWrapper(Site.RootWeb);
            }
        }

        /// <summary>
        /// A wrapper for <see cref="SPWeb.Title"/>.
        /// </summary>
        public string Title
        {
            get { return Web.Title; }
            set { Web.Title = value; }
        }

        /// <summary>
        /// A wrapper for <see cref="SPWeb.ID"/>.
        /// </summary>
        public Guid ID
        {
            get { return Web.ID; }
        }

        #region Web Properties

        [NonSerialized] private bool updatePropertiesPending;

        /// <summary>
        /// A wrapper method to <see cref="Web"/> object's <see cref="SPWeb.Properties"/> indexer.
        /// </summary>
        /// <param name="key">The key to use when fetching property value.</param>
        /// <returns>A string containing the value.</returns>
        public string GetProperty(string key)
        {
            return Web.Properties[key];
        }

        /// <summary>
        /// Sets the value in the <see cref="Web"/> object's <see cref="SPWeb.Properties"/>. Creates a new key, or updates an existing as needed.
        /// </summary>
        /// <param name="key">The key to use when storing the property value.</param>
        /// <param name="value">The value to set in the key.</param>
        /// <remarks>The property <see cref="UpdatePending"/> is set to true.</remarks>
        public void SetProperty(string key, string value)
        {
            if (!Web.Properties.ContainsKey(key))
            {
                Web.Properties.Add(key, value);
            }
            else
            {
                Web.Properties[key] = value;
            }
            updatePropertiesPending = true;
        }

        #endregion

        #region IDeserializationCallback Members

        ///<summary>
        ///Runs when the entire object graph has been deserialized.
        ///</summary>
        ///
        ///<param name="sender">The object that initiated the callback. The functionality for this parameter is not currently implemented. </param>
        public void OnDeserialization(object sender)
        {
            using (SPSite site = new SPSite(webUrl))
            {
                setWeb(site.OpenWeb(), true);
            }
        }

        #endregion

        #region IDisposable Members

        ///<summary>
        ///Closes inner web object if appropriate.
        ///</summary>
        ///<filterpriority>2</filterpriority>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void Dispose(bool isDisposing)
        {
            if (IsDisposed) return;
            if (isDisposing)
            {
                doDisposeOfWeb();
                IsDisposed = true;
            }
        }

        #endregion

        /// <summary>
        /// Value is true if <see cref="Dispose()"/> method has been called. Object is not in a usable state.
        /// </summary>
        internal bool IsDisposed
        {
            get; private set;
        }

        #region IEquatable<WebWrapper> Members

        /// <summary>
        /// This tests whether the two objects wraps the same web. It may however be two different instances of the same web.
        /// </summary>
        /// <param name="other">Another wrapper object.</param>
        /// <returns>True if <see cref="Uri"/> equals, false otherwise.</returns>
        public bool Equals(WebWrapper other)
        {
            if (other == null)
            {
                return false;
            }
            return Uri.Equals(other.Uri);
        }

        #endregion

        /// <summary>
        /// Reopens the inner <see cref="SPWeb"/> object. May be used when web object needs to be rereferenced in a new security context.
        /// </summary>
        public void ReOpen()
        {
            bool unsafeSetting = AllowUnsafeUpdatesSetting;
            using (SPSite site = new SPSite(Web.Url))
            {
                SPWeb newWeb = site.OpenWeb();
                doDisposeOfWeb();
                web = newWeb;
                web.AllowUnsafeUpdates = unsafeSetting;
                unsafeUpdatesSetting = false;
                webShouldBeClosed = true;
            }
        }

        private void doDisposeOfWeb()
        {
            if (Web == null) return;
            Update(true);
            if (webShouldBeClosed)
            {
                Web.Close();
            }
            else if (Web.Exists)
            {
                Web.AllowUnsafeUpdates = unsafeUpdatesSetting;
            }
            web = null;
        }

        /// <summary>
        /// Calls <see cref="SPWeb.Update"/> on the <see cref="Web"/> object.
        /// </summary>
        public void Update()
        {
            Update(false);
        }

        /// <summary>
        /// Sets <see cref="UpdatePending"/> to <c>true</c>.
        /// </summary>
        public void SetUpdatePending()
        {
            UpdatePending = true;
        }

        /// <summary>
        /// Calls <see cref="SPWeb.Update"/> on the <see cref="Web"/> object.
        /// <param name="onlyIfPending">If true, update will depend on state of the <see cref="UpdatePending"/> property.</param>
        /// </summary>
        public void Update(bool onlyIfPending)
        {
            if (onlyIfPending)
            {
                if (updatePropertiesPending)
                {
                    Web.Properties.Update();
                    updatePropertiesPending = false;
                }
                if (UpdatePending)
                {
                    Web.Update();
                    UpdatePending = false;
                }
            }
            else
            {
                Web.Update();
                UpdatePending = false;
            }
        }

        /// <summary>
        /// Returns the list from <see cref="Web"/> with <see cref="SPList.Title"/> equal to <see cref="title"/>.
        /// </summary>
        /// <param name="title">The <see cref="SPList.Title"/> of an existing list.</param>
        /// <returns>The first list found with the given title, or null, if no list is found.</returns>
        public SPList GetList(string title)
        {
            foreach (SPList list in Web.Lists)
            {
                if (list.Title == title)
                {
                    return list;
                }
            }
            return null;
        }
        /// <summary>
        /// A wrapper method to the <see cref="Web"/> object's <see cref="SPWeb.Lists"/> indexer. 
        /// </summary>
        /// <param name="id">The id of the list to return.</param>
        /// <returns>The list with the supplied id.</returns>
        public SPList GetList(Guid id)
        {
            return Web.Lists[id];
        }

        private void setWeb(SPWeb innerWeb, bool shouldBeClosed)
        {
            if (innerWeb == null || !innerWeb.Exists)
            {
                throw new ArgumentException("Web does not exist", "innerWeb");
            }
            web = innerWeb;
            webShouldBeClosed = shouldBeClosed;
            unsafeUpdatesSetting = innerWeb.AllowUnsafeUpdates;
            AllowUnsafeUpdates();
            webUrl = web.Url;
        }

        /// <summary>
        /// Creates a new <see cref="SPWeb"/> object using the
        /// url of the <see cref="web"/> parameter and wraps it
        /// in a new wrapper object. The web will be
        /// closed when the wrapper is disposed.
        /// The cloning is done using the <see cref="SPWeb.Url"/>, thus no security context is transferred to the new web.
        /// </summary>
        /// <remarks>Use this to create a clone of the context web.</remarks>
        /// <param name="web">The web to clone.</param>
        /// <returns>A new wrapper object.</returns>
        public static WebWrapper CloneOf(SPWeb web)
        {
            using (SPSite site = new SPSite(web.Url))
            {
                return new WebWrapper(site.OpenWeb());
            }
        }


        /// <summary>
        /// Creates a new <see cref="SPWeb"/> object using the
        /// <see cref="Web"/> of the <see cref="web"/> parameter and wraps it
        /// in a new wrapper object. The web will be
        /// closed when the wrapper is disposed.
        /// </summary>
        /// <remarks>Use this to create a clone of the context web.</remarks>
        /// <param name="web">The wrapper to clone.</param>
        /// <returns>A new wrapper object.</returns>
        public static WebWrapper CloneOf(WebWrapper web)
        {
            return CloneOf(web.Web);
        }

        /// <summary>
        /// Sets the AllowUnsafeUpdates property to true on the
        /// wrapped web object.
        /// <remarks>
        /// The setting is resat back in the dispose method, unless the
        /// web itself is closed.
        /// </remarks>
        /// </summary>
        public void AllowUnsafeUpdates()
        {
            Web.AllowUnsafeUpdates = true;
        }

        /// <summary>
        /// Returns the url of the inner web.
        /// </summary>
        /// <returns>A value that equals <see cref="Web"/> <see cref="SPWeb.Url"/> property.</returns>
        public override string ToString()
        {
            return webUrl;
        }

        /// <summary>
        /// Returns a new <see cref="WebWrapper"/> object wrapping a new copy of the inner <see cref="Web"/> object.
        /// The cloning is done using the <see cref="SPWeb.Url"/>, thus no security context is transferred to the new web.
        /// </summary>
        /// <remarks>The static method <see cref="CloneOf(SPWeb)"/> is used on the <see cref="Web"/> property.</remarks>
        /// <returns>A new wrapper.</returns>
        public WebWrapper Clone()
        {
            return CloneOf(Web);
        }

        /// <summary>
        /// Implicitly wraps the web object in a <see cref="WebWrapper"/> object.
        /// </summary>
        /// <param name="web">The web to wrap.</param>
        /// <returns>A new wrapper object. The original web may be accessed through the <see cref="Web"/> property.</returns>
        public static implicit operator WebWrapper(SPWeb web)
        {
            return new WebWrapper(web, false);
        }

        /// <summary>
        /// Explicitly extracts the <see cref="Web"/> value from the <see cref="wrapper"/>.
        /// </summary>
        /// <param name="wrapper">The object wrapping the <see cref="SPWeb"/> to extract.</param>
        /// <returns>The inner <see cref="Web"/> of <see cref="wrapper"/>.</returns>
        /// <remarks>The returned <see cref="SPWeb"/> object should be properly disposed after use.</remarks>
        public static explicit operator SPWeb(WebWrapper wrapper)
        {
            wrapper.DoNotDisposeInnerWeb();
            return wrapper.Web;
        }

        /// <summary>
        /// Wrapper method for <see cref="SPWeb.GetList"/> on <see cref="Web"/> object.
        /// </summary>
        /// <param name="uri">A site relative uri to the list.</param>
        /// <returns>A list if found.</returns>
        public SPList GetList(Uri uri)
        {
            return web.GetList(uri.ToString());
        }

        /// <summary>
        /// Wrapper method for <see cref="SPWeb.GetSiteData"/> on <see cref="Web"/> object.
        /// </summary>
        /// <returns>The results of the query,</returns>
        public DataTable GetSiteData(SPSiteDataQuery query)
        {
            return Web.GetSiteData(query);
        }

        /// <summary>
        /// Creates a new <see cref="SPWeb"/> as a sub web to this.
        /// </summary>
        /// <param name="url">The proposed local url of the new web. The nearest available is selected.</param>
        /// <param name="name">The title of the new web.</param>
        /// <param name="description">The description of the new web.</param>
        /// <param name="language">The language of the new web. <remarks>If the language is not supported, the language of this is used.</remarks></param>
        /// <param name="template">The site template to use.</param>
        /// <returns>The new web wrapped in a new <see cref="WebWrapper"/> object.</returns>
        [DebuggerStepThrough]
        //debugger step through is to prevent this method to break when debugging, as it throws exceptions by [poor] design.
        public WebWrapper CreateSubWeb(string url, string name, string description, uint language,
                                       string template)
        {
            SPWeb newWeb;
            try
            {
                newWeb = Web.Webs.Add(findSuitableWebUrl(url), name, description, language, template, true, false);
            }
            catch (SPException err)
            {
                if (err.ErrorCode == -2130575266)
                {
                    //language not supported. Fallback to parent web language
                    newWeb = Web.Webs.Add(findSuitableWebUrl(url), name, description, Web.Language, template, true,
                                          false);
                }
                else
                    throw;
            }
            return new WebWrapper(newWeb);
        }

        private string findSuitableWebUrl(string proposedName)
        {
            StringCollection names = new StringCollection();
            names.AddRange(Web.Webs.Names);
            int suffixIndex = 0;
            const int maxIterations = 100000;
            string name = proposedName;
            while (names.Contains(name) && suffixIndex < maxIterations)
            {
                name = string.Format("{0}_{1}", proposedName, suffixIndex++);
            }
            return name;
        }

        /// <summary>
        /// Calling this method will inhibit the default behaviour of closing the web on disposal.
        /// </summary>
        /// <remarks>Use with caution.</remarks>
        internal void DoNotDisposeInnerWeb()
        {
            webShouldBeClosed = false;
        }

    }
}

I use a wrapper class for handling most manipulation of SPWeb objects. This helps me remember to close the web, and it eases the problems of unsafeupdates setting. It is a bit bloated, as I have patched on new constructors and members. but, then again; so is the SPWeb class.

Usage:

using (WebWrapper wrapper = new WebWrapper("http://localhost"))
            {
                wrapper.AllowUnsafeUpdates();

                //Do work on wrapper.
            }

The class definition:

using System;
using System.Collections.Specialized;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.Serialization;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace Skaar.SharePoint.Customization
{
    /// <summary>
    /// A wrapper for a <see cref="SPWeb"/> object.
    /// <remarks>Closes web object on Dispose if applicable.</remarks>
    /// </summary>
    [Serializable]
    [DebuggerDisplay("{Uri} Unsafe:{AllowUnsafeUpdatesSetting} Update:{UpdatePending}")]
    public sealed class WebWrapper : IDisposable, IDeserializationCallback, IEquatable<WebWrapper>
    {
        [NonSerialized] private bool unsafeUpdatesSetting;

        [NonSerialized] private SPWeb web;

        /// <summary>
        /// Determines if the inner web object should be closed.
        /// </summary>
        [NonSerialized] private bool webShouldBeClosed;

        /// <summary>
        /// This value is used in serialization to restore <see cref="Web"/>.
        /// </summary>
        private string webUrl;

        /// <summary>
        /// Creates a new wrapper object.
        /// </summary>
        /// <param name="web">A web that should be closed/disposed when done.</param>
        public WebWrapper(SPWeb web) : this(web, true)
        {
        }

        /// <summary>
        /// Creates a new wrapper object.
        /// </summary>
        /// <param name="web">An inner web object</param>
        /// <param name="webShouldBeClosed">If true, the web object is closed in the <see cref="Dispose()"/> method.</param>
        public WebWrapper(SPWeb web, bool webShouldBeClosed)
        {
            setWeb(web, webShouldBeClosed);
        }

        /// <summary>
        /// Creates a new wrapper object.
        /// </summary>
        /// <param name="webAddress">The address to a web.</param>
        public WebWrapper(Uri webAddress)
        {
            using (SPSite site = new SPSite(webAddress.ToString()))
            {
                string relativeUrl = renderWebRootRelativeUrl(webAddress);
                if (relativeUrl == null)
                {
                    setWeb(site.OpenWeb(), true);
                }
                else
                {
                    setWeb(site.OpenWeb(relativeUrl), true);
                }
            }
        }

        private string renderWebRootRelativeUrl(Uri address)
        {
            for (int i = 0; i < address.Segments.Length; i++)
            {
                string segment = address.Segments[i];
                if (string.Equals(segment, "_layouts/"))
                {
                    string newUrl=string.Join(null, address.Segments, 0, i).Trim('/');
                    return newUrl;
                }
            }
            return null;
        }

        /// <summary>
        /// If true, <see cref="SPWeb.Update"/> will be called in <see cref="Dispose()"/>.
        /// </summary>
        public bool UpdatePending { get; private set; }

        /// <summary>
        /// The setting of the inner web (<see cref="SPWeb.AllowUnsafeUpdates"/>)
        /// </summary>
        public bool AllowUnsafeUpdatesSetting
        {
            get { return Web.AllowUnsafeUpdates; }
        }

        /// <summary>
        /// The inner object.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Exception is thrown if <see cref="IsDisposed"/> is true.</exception>
        public SPWeb Web
        {
            get
            {
                if(IsDisposed)
                {
                    throw new ObjectDisposedException("Web wrapper is disposed.");
                }
                return web;
            }
        }

        /// <summary>
        /// The address of the <see cref="Web"/> wrapped as a <see cref="Uri"/> object.
        /// </summary>
        public Uri Uri
        {
            get { return new Uri(Web.Url); }
        }

        /// <summary>
        /// The address of the <see cref="Web"/> wrapped as a <see cref="Uri"/> object.
        /// </summary>
        public Uri GetUri(SPUrlZone zone)
        {
            return Site.WebApplication.GetResponseUri(zone, Uri.AbsolutePath);
        }

        /// <summary>
        /// Creates a wrapper around the context web.
        /// <remarks>The web will not be closed when wrapper is disposed. Returns null if context is unavailable.</remarks>
        /// </summary>
        public static WebWrapper Context
        {
            get
            {
                return SPContext.Current==null?null:new WebWrapper(SPContext.Current.Web, false);
            }
        }

        /// <summary>
        /// This is a static property wrapping of
        /// the <see cref="CloneOf(SPWeb)"/> method, using
        /// the <see cref="SPContext"/> current web as
        /// parameter.
        /// <remarks>Returns null if context is unavailable.</remarks>
        /// </summary>
        public static WebWrapper CloneOfContext
        {
            get
            {
                if (SPContext.Current != null)
                {
                    SPWeb contextWeb = SPContext.Current.Web;
                    return CloneOf(contextWeb);
                }
                return null;
            }
        }

        /// <summary>
        /// Returns the <see cref="SPWeb.Exists"/> property of the <see cref="Web"/> object.
        /// </summary>
        public bool Exists
        {
            get { return Web != null && Web.Exists; }
        }

        /// <summary>
        /// Gets the <see cref="SPSite"/> object of <see cref="Web"/>.
        /// </summary>
        /// <remarks>This object should not be closed by user code.</remarks>
        public SPSite Site
        {
            get { return web.Site; }
        }

        /// <summary>
        /// Gets the owner defined in <see cref="SPSite.Owner"/>.
        /// </summary>
        public SPUser Owner
        {
            get
            {
                return Site.Owner;
            }
        }

        /// <summary>
        /// Returns a context of the inner <see cref="Web"/>.
        /// </summary>
        public SPContext ContextOfWeb
        {
            get { return SPContext.GetContext(web); }
        }

        /// <summary>
        /// Gets the language of <see cref="Web"/>.
        /// </summary>
        public CultureInfo Locale
        {
            get { return Web.Locale; }
        }

        /// <summary>
        /// Gets the language of the root web.
        /// </summary>
        public CultureInfo LocaleOfRoot
        {
            get
            {
                using (WebWrapper root = Root)
                {
                    return root.Locale;
                }
            }
        }

        /// <summary>
        /// Returns a new <see cref="WebWrapper"/> wrapping the root <see cref="SPWeb"/> of this.
        /// </summary>
        public WebWrapper Root
        {
            get
            {
                if (webShouldBeClosed)
                    using (SPSite site = Site)
                    {
                        return new WebWrapper(site.RootWeb);
                    }
                return new WebWrapper(Site.RootWeb);
            }
        }

        /// <summary>
        /// A wrapper for <see cref="SPWeb.Title"/>.
        /// </summary>
        public string Title
        {
            get { return Web.Title; }
            set { Web.Title = value; }
        }

        /// <summary>
        /// A wrapper for <see cref="SPWeb.ID"/>.
        /// </summary>
        public Guid ID
        {
            get { return Web.ID; }
        }

        #region Web Properties

        [NonSerialized] private bool updatePropertiesPending;

        /// <summary>
        /// A wrapper method to <see cref="Web"/> object's <see cref="SPWeb.Properties"/> indexer.
        /// </summary>
        /// <param name="key">The key to use when fetching property value.</param>
        /// <returns>A string containing the value.</returns>
        public string GetProperty(string key)
        {
            return Web.Properties[key];
        }

        /// <summary>
        /// Sets the value in the <see cref="Web"/> object's <see cref="SPWeb.Properties"/>. Creates a new key, or updates an existing as needed.
        /// </summary>
        /// <param name="key">The key to use when storing the property value.</param>
        /// <param name="value">The value to set in the key.</param>
        /// <remarks>The property <see cref="UpdatePending"/> is set to true.</remarks>
        public void SetProperty(string key, string value)
        {
            if (!Web.Properties.ContainsKey(key))
            {
                Web.Properties.Add(key, value);
            }
            else
            {
                Web.Properties[key] = value;
            }
            updatePropertiesPending = true;
        }

        #endregion

        #region IDeserializationCallback Members

        ///<summary>
        ///Runs when the entire object graph has been deserialized.
        ///</summary>
        ///
        ///<param name="sender">The object that initiated the callback. The functionality for this parameter is not currently implemented. </param>
        public void OnDeserialization(object sender)
        {
            using (SPSite site = new SPSite(webUrl))
            {
                setWeb(site.OpenWeb(), true);
            }
        }

        #endregion

        #region IDisposable Members

        ///<summary>
        ///Closes inner web object if appropriate.
        ///</summary>
        ///<filterpriority>2</filterpriority>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void Dispose(bool isDisposing)
        {
            if (IsDisposed) return;
            if (isDisposing)
            {
                doDisposeOfWeb();
                IsDisposed = true;
            }
        }

        #endregion

        /// <summary>
        /// Value is true if <see cref="Dispose()"/> method has been called. Object is not in a usable state.
        /// </summary>
        internal bool IsDisposed
        {
            get; private set;
        }

        #region IEquatable<WebWrapper> Members

        /// <summary>
        /// This tests whether the two objects wraps the same web. It may however be two different instances of the same web.
        /// </summary>
        /// <param name="other">Another wrapper object.</param>
        /// <returns>True if <see cref="Uri"/> equals, false otherwise.</returns>
        public bool Equals(WebWrapper other)
        {
            if (other == null)
            {
                return false;
            }
            return Uri.Equals(other.Uri);
        }

        #endregion

        /// <summary>
        /// Reopens the inner <see cref="SPWeb"/> object. May be used when web object needs to be rereferenced in a new security context.
        /// </summary>
        public void ReOpen()
        {
            bool unsafeSetting = AllowUnsafeUpdatesSetting;
            using (SPSite site = new SPSite(Web.Url))
            {
                SPWeb newWeb = site.OpenWeb();
                doDisposeOfWeb();
                web = newWeb;
                web.AllowUnsafeUpdates = unsafeSetting;
                unsafeUpdatesSetting = false;
                webShouldBeClosed = true;
            }
        }

        private void doDisposeOfWeb()
        {
            if (Web == null) return;
            Update(true);
            if (webShouldBeClosed)
            {
                Web.Close();
            }
            else if (Web.Exists)
            {
                Web.AllowUnsafeUpdates = unsafeUpdatesSetting;
            }
            web = null;
        }

        /// <summary>
        /// Calls <see cref="SPWeb.Update"/> on the <see cref="Web"/> object.
        /// </summary>
        public void Update()
        {
            Update(false);
        }

        /// <summary>
        /// Sets <see cref="UpdatePending"/> to <c>true</c>.
        /// </summary>
        public void SetUpdatePending()
        {
            UpdatePending = true;
        }

        /// <summary>
        /// Calls <see cref="SPWeb.Update"/> on the <see cref="Web"/> object.
        /// <param name="onlyIfPending">If true, update will depend on state of the <see cref="UpdatePending"/> property.</param>
        /// </summary>
        public void Update(bool onlyIfPending)
        {
            if (onlyIfPending)
            {
                if (updatePropertiesPending)
                {
                    Web.Properties.Update();
                    updatePropertiesPending = false;
                }
                if (UpdatePending)
                {
                    Web.Update();
                    UpdatePending = false;
                }
            }
            else
            {
                Web.Update();
                UpdatePending = false;
            }
        }

        /// <summary>
        /// Returns the list from <see cref="Web"/> with <see cref="SPList.Title"/> equal to <see cref="title"/>.
        /// </summary>
        /// <param name="title">The <see cref="SPList.Title"/> of an existing list.</param>
        /// <returns>The first list found with the given title, or null, if no list is found.</returns>
        public SPList GetList(string title)
        {
            foreach (SPList list in Web.Lists)
            {
                if (list.Title == title)
                {
                    return list;
                }
            }
            return null;
        }
        /// <summary>
        /// A wrapper method to the <see cref="Web"/> object's <see cref="SPWeb.Lists"/> indexer. 
        /// </summary>
        /// <param name="id">The id of the list to return.</param>
        /// <returns>The list with the supplied id.</returns>
        public SPList GetList(Guid id)
        {
            return Web.Lists[id];
        }

        private void setWeb(SPWeb innerWeb, bool shouldBeClosed)
        {
            if (innerWeb == null || !innerWeb.Exists)
            {
                throw new ArgumentException("Web does not exist", "innerWeb");
            }
            web = innerWeb;
            webShouldBeClosed = shouldBeClosed;
            unsafeUpdatesSetting = innerWeb.AllowUnsafeUpdates;
            AllowUnsafeUpdates();
            webUrl = web.Url;
        }

        /// <summary>
        /// Creates a new <see cref="SPWeb"/> object using the
        /// url of the <see cref="web"/> parameter and wraps it
        /// in a new wrapper object. The web will be
        /// closed when the wrapper is disposed.
        /// The cloning is done using the <see cref="SPWeb.Url"/>, thus no security context is transferred to the new web.
        /// </summary>
        /// <remarks>Use this to create a clone of the context web.</remarks>
        /// <param name="web">The web to clone.</param>
        /// <returns>A new wrapper object.</returns>
        public static WebWrapper CloneOf(SPWeb web)
        {
            using (SPSite site = new SPSite(web.Url))
            {
                return new WebWrapper(site.OpenWeb());
            }
        }


        /// <summary>
        /// Creates a new <see cref="SPWeb"/> object using the
        /// <see cref="Web"/> of the <see cref="web"/> parameter and wraps it
        /// in a new wrapper object. The web will be
        /// closed when the wrapper is disposed.
        /// </summary>
        /// <remarks>Use this to create a clone of the context web.</remarks>
        /// <param name="web">The wrapper to clone.</param>
        /// <returns>A new wrapper object.</returns>
        public static WebWrapper CloneOf(WebWrapper web)
        {
            return CloneOf(web.Web);
        }

        /// <summary>
        /// Sets the AllowUnsafeUpdates property to true on the
        /// wrapped web object.
        /// <remarks>
        /// The setting is resat back in the dispose method, unless the
        /// web itself is closed.
        /// </remarks>
        /// </summary>
        public void AllowUnsafeUpdates()
        {
            Web.AllowUnsafeUpdates = true;
        }

        /// <summary>
        /// Returns the url of the inner web.
        /// </summary>
        /// <returns>A value that equals <see cref="Web"/> <see cref="SPWeb.Url"/> property.</returns>
        public override string ToString()
        {
            return webUrl;
        }

        /// <summary>
        /// Returns a new <see cref="WebWrapper"/> object wrapping a new copy of the inner <see cref="Web"/> object.
        /// The cloning is done using the <see cref="SPWeb.Url"/>, thus no security context is transferred to the new web.
        /// </summary>
        /// <remarks>The static method <see cref="CloneOf(SPWeb)"/> is used on the <see cref="Web"/> property.</remarks>
        /// <returns>A new wrapper.</returns>
        public WebWrapper Clone()
        {
            return CloneOf(Web);
        }

        /// <summary>
        /// Implicitly wraps the web object in a <see cref="WebWrapper"/> object.
        /// </summary>
        /// <param name="web">The web to wrap.</param>
        /// <returns>A new wrapper object. The original web may be accessed through the <see cref="Web"/> property.</returns>
        public static implicit operator WebWrapper(SPWeb web)
        {
            return new WebWrapper(web, false);
        }

        /// <summary>
        /// Explicitly extracts the <see cref="Web"/> value from the <see cref="wrapper"/>.
        /// </summary>
        /// <param name="wrapper">The object wrapping the <see cref="SPWeb"/> to extract.</param>
        /// <returns>The inner <see cref="Web"/> of <see cref="wrapper"/>.</returns>
        /// <remarks>The returned <see cref="SPWeb"/> object should be properly disposed after use.</remarks>
        public static explicit operator SPWeb(WebWrapper wrapper)
        {
            wrapper.DoNotDisposeInnerWeb();
            return wrapper.Web;
        }

        /// <summary>
        /// Wrapper method for <see cref="SPWeb.GetList"/> on <see cref="Web"/> object.
        /// </summary>
        /// <param name="uri">A site relative uri to the list.</param>
        /// <returns>A list if found.</returns>
        public SPList GetList(Uri uri)
        {
            return web.GetList(uri.ToString());
        }

        /// <summary>
        /// Wrapper method for <see cref="SPWeb.GetSiteData"/> on <see cref="Web"/> object.
        /// </summary>
        /// <returns>The results of the query,</returns>
        public DataTable GetSiteData(SPSiteDataQuery query)
        {
            return Web.GetSiteData(query);
        }

        /// <summary>
        /// Creates a new <see cref="SPWeb"/> as a sub web to this.
        /// </summary>
        /// <param name="url">The proposed local url of the new web. The nearest available is selected.</param>
        /// <param name="name">The title of the new web.</param>
        /// <param name="description">The description of the new web.</param>
        /// <param name="language">The language of the new web. <remarks>If the language is not supported, the language of this is used.</remarks></param>
        /// <param name="template">The site template to use.</param>
        /// <returns>The new web wrapped in a new <see cref="WebWrapper"/> object.</returns>
        [DebuggerStepThrough]
        //debugger step through is to prevent this method to break when debugging, as it throws exceptions by [poor] design.
        public WebWrapper CreateSubWeb(string url, string name, string description, uint language,
                                       string template)
        {
            SPWeb newWeb;
            try
            {
                newWeb = Web.Webs.Add(findSuitableWebUrl(url), name, description, language, template, true, false);
            }
            catch (SPException err)
            {
                if (err.ErrorCode == -2130575266)
                {
                    //language not supported. Fallback to parent web language
                    newWeb = Web.Webs.Add(findSuitableWebUrl(url), name, description, Web.Language, template, true,
                                          false);
                }
                else
                    throw;
            }
            return new WebWrapper(newWeb);
        }

        private string findSuitableWebUrl(string proposedName)
        {
            StringCollection names = new StringCollection();
            names.AddRange(Web.Webs.Names);
            int suffixIndex = 0;
            const int maxIterations = 100000;
            string name = proposedName;
            while (names.Contains(name) && suffixIndex < maxIterations)
            {
                name = string.Format("{0}_{1}", proposedName, suffixIndex++);
            }
            return name;
        }

        /// <summary>
        /// Calling this method will inhibit the default behaviour of closing the web on disposal.
        /// </summary>
        /// <remarks>Use with caution.</remarks>
        internal void DoNotDisposeInnerWeb()
        {
            webShouldBeClosed = false;
        }

    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文