I have Entity
/// <summary>
/// The greoup.
/// </summary>
public class Group
{
#region Public Properties
/// <summary>
/// Gets or sets the group id.
/// </summary>
[Key]
public int GroupId { get; set; }
/// <summary>
/// Gets or sets the parent group id.
/// </summary>
public int ParentGroupId { get; set; }
/// <summary>
/// Gets or sets the active.
/// </summary>
public int Active { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
public string Description { get; set; }
/// <summary>
/// Gets or sets the group guid.
/// </summary>
public Guid GroupGuid { get; set; }
/// <summary>
/// Gets or sets the order weight.
/// </summary>
public int OrderWeight { get; set; }
/// <summary>
/// Gets or sets the parent group.
/// </summary>
[ForeignKey("ParentGroupId")]
public virtual Group ParentGroup { get; set; }
/// <summary>
/// Gets or sets the groups.
/// </summary>
public virtual ICollection<Group> Groups { get; set; }
#endregion
}
How I can to allow exited foreign key. Because when I try add Group when ParentGroupId = 0. I get exception
Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.
you are creating a self referencing table in the database with this structure. So the ParentGroupId must be nullable. The root node cannot have a parent , all the nodes arising from it will however have a parent, so you need to make the ParentGroupId nullable.
I was created two class.
[Table("SrvCenters")]
public class ManagerServiceCenter
{
#region Public Properties
/// <summary>
/// Gets or sets the srv center id.
/// </summary>
[Key]
public int SrvCenterId { get; set; }
/// <summary>
/// Gets or sets the branch id.
/// </summary>
public int BranchId { get; set; }
/// <summary>
/// Gets or sets the branch.
/// </summary>
public virtual Branch Branch { get; set; }
/// <summary>
/// Gets or sets the location id.
/// </summary>
public int LocationId { get; set; }
/// <summary>
/// Gets or sets the location.
/// </summary>
public virtual Location Location { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
public string Description { get; set; }
/// <summary>
/// Gets or sets the domain name.
/// </summary>
public string DomainName { get; set; }
/// <summary>
/// Gets or sets the active.
/// </summary>
public int Active { get; set; }
#endregion
}
and
[Table("SrvCenters")]
public class AdminServiceCenter
{
#region Public Properties
/// <summary>
/// Gets or sets the srv center id.
/// </summary>
[Key]
public int SrvCenterId { get; set; }
/// <summary>
/// Gets or sets the active.
/// </summary>
public int Active { get; set; }
/// <summary>
/// Gets or sets the begin break.
/// </summary>
public TimeSpan BeginBreak { get; set; }
/// <summary>
/// Gets or sets the begin day.
/// </summary>
public TimeSpan BeginDay { get; set; }
/// <summary>
/// Gets or sets the branch id.
/// </summary>
public int BranchId { get; set; }
/// <summary>
/// Gets or sets the branch.
/// </summary>
public virtual Branch Branch { get; set; }
/// <summary>
/// Gets or sets a value indicating whether check time table.
/// </summary>
public int CheckTimeTable { get; set; }
/// <summary>
/// Gets or sets the create time.
/// </summary>
public DateTime? CreateTime { get; set; }
/// <summary>
/// Gets or sets the create user.
/// </summary>
public string CreateUser { get; set; }
/// <summary>
/// Gets or sets the cust priority id.
/// </summary>
public int CustPriorityId { get; set; }
/// <summary>
/// Gets or sets the customer bind id.
/// </summary>
public int CustomerBindId { get; set; }
/// <summary>
/// Gets or sets the delete count.
/// </summary>
public int DeleteCount { get; set; }
/// <summary>
/// Gets or sets the delete time.
/// </summary>
public int DeleteTime { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
public string Description { get; set; }
/// <summary>
/// Gets or sets the domain name.
/// </summary>
public string DomainName { get; set; }
/// <summary>
/// Gets or sets the end break.
/// </summary>
public TimeSpan EndBreak { get; set; }
/// <summary>
/// Gets or sets the end day.
/// </summary>
public TimeSpan EndDay { get; set; }
/// <summary>
/// Gets or sets a value indicating whether estimate wait time.
/// </summary>
public int EstimateWaitTime { get; set; }
/// <summary>
/// Gets or sets a value indicating whether have break.
/// </summary>
public int HaveBreak { get; set; }
/// <summary>
/// Gets or sets the hold order.
/// </summary>
public int HoldOrder { get; set; }
/// <summary>
/// Gets or sets the location id.
/// </summary>
public int LocationId { get; set; }
/// <summary>
/// Gets or sets the notif interval.
/// </summary>
public int NotifInterval { get; set; }
/// <summary>
/// Gets or sets a value indicating whether notification.
/// </summary>
public int Notification { get; set; }
/// <summary>
/// Gets or sets the priority direct id.
/// </summary>
public int PriorityDirectId { get; set; }
/// <summary>
/// Gets or sets the serv priority id.
/// </summary>
public int ServPriorityId { get; set; }
/// <summary>
/// Gets or sets the service bind id.
/// </summary>
public int ServiceBindId { get; set; }
/// <summary>
/// Gets or sets the srv center guid.
/// </summary>
public Guid SrvCenterGuid { get; set; }
/// <summary>
/// Gets or sets the sequence id.
/// </summary>
public int SequenceId { get; set; }
/// <summary>
/// Gets or sets the sequence.
/// </summary>
public virtual Sequence Sequence { get; set; }
#endregion
}
When try I called ManagerServiceCenter. I got exception
The entity types 'AdminServiceCenter' and 'ManagerServiceCenter' cannot share table 'SrvCenters' because they are not in the same type hierarchy or do not have a valid one to one foreign key relationship with matching primary keys between them.
Ok
Why this class working :)
/// <summary>
/// The workplace setting.
/// </summary>
[Table("SrvCenters")]
public class WorkplaceSetting
{
/// <summary>
/// Gets or sets the srv center id.
/// </summary>
[Key]
public int SrvCenterId { get; set; }
/// <summary>
/// Gets or sets the net ident type id.
/// </summary>
public int NetIdentTypeId { get; set; }
/// <summary>
/// Gets or sets the help net ident type.
/// </summary>
[ForeignKey("NetIdentTypeId")]
public virtual HelpNetIdentType HelpNetIdentType { get; set; }
/// <summary>
/// Gets or sets the w p_ auto start exec.
/// </summary>
public int WP_AutoStartExec { get; set; }
/// <summary>
/// Gets or sets the w p_ button mask.
/// </summary>
public int WP_ButtonMask { get; set; }
/// <summary>
/// Gets or sets the w p_ show info.
/// </summary>
public int WP_ShowInfo { get; set; }
/// <summary>
/// Gets or sets the w p_ show message.
/// </summary>
public int WP_ShowMessage { get; set; }
/// <summary>
/// Gets or sets the w p_ time between msg.
/// </summary>
public int WP_TimeBetweenMsg { get; set; }
/// <summary>
/// Gets or sets the w p_ time in idle.
/// </summary>
public int WP_TimeInIdle { get; set; }
/// <summary>
/// Gets or sets the w p_ time in resolve.
/// </summary>
public int WP_TimeInResolve { get; set; }
/// <summary>
/// Gets or sets the w p_ time show msg.
/// </summary>
public int WP_TimeShowMsg { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
public string Description { get; set; }
/// <summary>
/// Gets or sets the active.
/// </summary>
public int Active { get; set; }
}
You must use TPH inheritance or table splitting if you want to share table between two entities.
I am trying to extend webservice plygin in order to get from nopcommerce the List of Products.
What I have done is the following. I created a class like ProductDto, and inside NopService.cs I created a method like:
public List<ProductDto> GetProductCollection(string usernameOrEmail, string userPassword)
ProductDto is taken from Product using AutoMapper. (shown below)
BUT IT DOES NOT WORK :( what am I missing?
Any ideas? The idea behind all this is to connect nopcommerce with my ERP through webservices
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Nop.Core.Domain.Catalog
{
public class ProductDto
{
public virtual string Name { get; set; }
// Instance members must be virtual on data table objects like Affiliate.cs
// Virtual is required by data access frameworks so that these frameworks
// can implement more complex features like lazy loading.
public virtual string ProductGID { get; set; }
/// <summary>
/// Gets or sets the short description
/// </summary>
public virtual string ShortDescription { get; set; }
/// <summary>
/// Gets or sets the full description
/// </summary>
public virtual string FullDescription { get; set; }
/// <summary>
/// Gets or sets the admin comment
/// </summary>
public virtual string AdminComment { get; set; }
/// <summary>
/// Gets or sets a value of used product template identifier
/// </summary>
public virtual int ProductTemplateId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to show the product on home page
/// </summary>
public virtual bool ShowOnHomePage { get; set; }
/// <summary>
/// Gets or sets the meta keywords
/// </summary>
public virtual string MetaKeywords { get; set; }
/// <summary>
/// Gets or sets the meta description
/// </summary>
public virtual string MetaDescription { get; set; }
/// <summary>
/// Gets or sets the meta title
/// </summary>
public virtual string MetaTitle { get; set; }
/// <summary>
/// Gets or sets the search-engine name
/// </summary>
public virtual string SeName { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the product allows customer reviews
/// </summary>
public virtual bool AllowCustomerReviews { get; set; }
/// <summary>
/// Gets or sets the rating sum (approved reviews)
/// </summary>
public virtual int ApprovedRatingSum { get; set; }
/// <summary>
/// Gets or sets the rating sum (not approved reviews)
/// </summary>
public virtual int NotApprovedRatingSum { get; set; }
/// <summary>
/// Gets or sets the total rating votes (approved reviews)
/// </summary>
public virtual int ApprovedTotalReviews { get; set; }
/// <summary>
/// Gets or sets the total rating votes (not approved reviews)
/// </summary>
public virtual int NotApprovedTotalReviews { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity is published
/// </summary>
public virtual bool Published { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity has been deleted
/// </summary>
public virtual bool Deleted { get; set; }
/// <summary>
/// Gets or sets the date and time of product creation
/// </summary>
public virtual DateTime CreatedOnUtc { get; set; }
/// <summary>
/// Gets or sets the date and time of product update
/// </summary>
public virtual DateTime UpdatedOnUtc { get; set; }
}
}
And inside NopService.cs I created a method like this
public List<ProductDto> GetProductCollection(string usernameOrEmail, string userPassword)
{
CheckAccess(usernameOrEmail, userPassword);
if (!_permissionSettings.Authorize(StandardPermissionProvider.ManageCatalog))
throw new ApplicationException("Not allowed to manage Catalog");
var productslist = new List<Product>();
productslist.AddRange(_productService.GetAllProducts(false));
List<Product> products = productslist;
List<ProductDto> productsDtos = Mapper.Map<List<Product>, List<ProductDto>>(products);
return productsDtos;
}
}
Your mapping should only be configured once, during startup. So try moving it to a profile:
namespace MyNamespace
{
using System.Collections.Generic;
using AutoMapper;
public class ProductProfile : Profile
{
public override string ProfileName
{
get
{
return "ProductProfile";
}
}
protected override void Configure()
{
Mapper.CreateMap<Product, ProductDto>();
Mapper.CreateMap<ProductCategory, ProductCategoryDto>();
// etc
}
}
}
Then initialise it during start up (Application_Start, etc):
Mapper.Initialize(m => m.AddProfile<ProductProfile>());
Create a unit test to check your mappings:
namespace MyNamespace
{
using System.Collections.Generic;
using AutoMapper;
using NUnit.Framework;
[TestFixture]
public class AutoMapperTests
{
[Test]
public void AutoMapper_Configuration_IsValid()
{
Mapper.Initialize(m => m.AddProfile<ProductProfile>());
Mapper.AssertConfigurationIsValid();
}
}
}
This will help determine if there are any errors in your mapping configuration, and show you what to fix. Once this is done, you can then focus on getting the functionality working.
I managed to make it work but with problems in mapping nested classes.
Product class
public partial class Product : BaseEntity, ILocalizedEntity
{
private ICollection<ProductVariant> _productVariants;
private ICollection<ProductCategory> _productCategories;
private ICollection<ProductManufacturer> _productManufacturers;
private ICollection<ProductPicture> _productPictures;
private ICollection<ProductReview> _productReviews;
private ICollection<ProductSpecificationAttribute> _productSpecificationAttributes;
private ICollection<ProductTag> _productTags;
/// <summary>
/// Gets or sets the name
/// </summary>
public virtual string Name { get; set; }
// Instance members must be virtual on data table objects like Affiliate.cs
// Virtual is required by data access frameworks so that these frameworks
// can implement more complex features like lazy loading.
public virtual string ProductGID { get; set; }
/// <summary>
/// Gets or sets the short description
/// </summary>
public virtual string ShortDescription { get; set; }
/// <summary>
/// Gets or sets the full description
/// </summary>
public virtual string FullDescription { get; set; }
/// <summary>
/// Gets or sets the admin comment
/// </summary>
public virtual string AdminComment { get; set; }
/// <summary>
/// Gets or sets a value of used product template identifier
/// </summary>
public virtual int ProductTemplateId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to show the product on home page
/// </summary>
public virtual bool ShowOnHomePage { get; set; }
/// <summary>
/// Gets or sets the meta keywords
/// </summary>
public virtual string MetaKeywords { get; set; }
/// <summary>
/// Gets or sets the meta description
/// </summary>
public virtual string MetaDescription { get; set; }
/// <summary>
/// Gets or sets the meta title
/// </summary>
public virtual string MetaTitle { get; set; }
/// <summary>
/// Gets or sets the search-engine name
/// </summary>
public virtual string SeName { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the product allows customer reviews
/// </summary>
public virtual bool AllowCustomerReviews { get; set; }
/// <summary>
/// Gets or sets the rating sum (approved reviews)
/// </summary>
public virtual int ApprovedRatingSum { get; set; }
/// <summary>
/// Gets or sets the rating sum (not approved reviews)
/// </summary>
public virtual int NotApprovedRatingSum { get; set; }
/// <summary>
/// Gets or sets the total rating votes (approved reviews)
/// </summary>
public virtual int ApprovedTotalReviews { get; set; }
/// <summary>
/// Gets or sets the total rating votes (not approved reviews)
/// </summary>
public virtual int NotApprovedTotalReviews { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity is published
/// </summary>
public virtual bool Published { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity has been deleted
/// </summary>
public virtual bool Deleted { get; set; }
/// <summary>
/// Gets or sets the date and time of product creation
/// </summary>
public virtual DateTime CreatedOnUtc { get; set; }
/// <summary>
/// Gets or sets the date and time of product update
/// </summary>
public virtual DateTime UpdatedOnUtc { get; set; }
/// <summary>
/// Gets or sets the product variants
/// </summary>
public virtual ICollection<ProductVariant> ProductVariants
{
get { return _productVariants ?? (_productVariants = new List<ProductVariant>()); }
protected set { _productVariants = value; }
}
/// <summary>
/// Gets or sets the collection of ProductCategory
/// </summary>
public virtual ICollection<ProductCategory> ProductCategories
{
get { return _productCategories ?? (_productCategories = new List<ProductCategory>()); }
protected set { _productCategories = value; }
}
/// <summary>
/// Gets or sets the collection of ProductManufacturer
/// </summary>
public virtual ICollection<ProductManufacturer> ProductManufacturers
{
get { return _productManufacturers ?? (_productManufacturers = new List<ProductManufacturer>()); }
protected set { _productManufacturers = value; }
}
/// <summary>
/// Gets or sets the collection of ProductPicture
/// </summary>
public virtual ICollection<ProductPicture> ProductPictures
{
get { return _productPictures ?? (_productPictures = new List<ProductPicture>()); }
protected set { _productPictures = value; }
}
/// <summary>
/// Gets or sets the collection of product reviews
/// </summary>
public virtual ICollection<ProductReview> ProductReviews
{
get { return _productReviews ?? (_productReviews = new List<ProductReview>()); }
protected set { _productReviews = value; }
}
/// <summary>
/// Gets or sets the product specification attribute
/// </summary>
public virtual ICollection<ProductSpecificationAttribute> ProductSpecificationAttributes
{
get { return _productSpecificationAttributes ?? (_productSpecificationAttributes = new List<ProductSpecificationAttribute>()); }
protected set { _productSpecificationAttributes = value; }
}
/// <summary>
/// Gets or sets the product specification attribute
/// </summary>
public virtual ICollection<ProductTag> ProductTags
{
get { return _productTags ?? (_productTags = new List<ProductTag>()); }
protected set { _productTags = value; }
}
}
}
ProductDto Class
public class ProductDto
{
private ICollection<ProductCategoryDto> _productCategories;
public virtual string Name { get; set; }
// Instance members must be virtual on data table objects like Affiliate.cs
// Virtual is required by data access frameworks so that these frameworks
// can implement more complex features like lazy loading.
public virtual string ProductGID { get; set; }
/// <summary>
/// Gets or sets the short description
/// </summary>
public virtual string ShortDescription { get; set; }
/// <summary>
/// Gets or sets the full description
/// </summary>
public virtual string FullDescription { get; set; }
/// <summary>
/// Gets or sets the admin comment
/// </summary>
public virtual string AdminComment { get; set; }
/// <summary>
/// Gets or sets a value of used product template identifier
/// </summary>
public virtual int ProductTemplateId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to show the product on home page
/// </summary>
public virtual bool ShowOnHomePage { get; set; }
/// <summary>
/// Gets or sets the meta keywords
/// </summary>
public virtual string MetaKeywords { get; set; }
/// <summary>
/// Gets or sets the meta description
/// </summary>
public virtual string MetaDescription { get; set; }
/// <summary>
/// Gets or sets the meta title
/// </summary>
public virtual string MetaTitle { get; set; }
/// <summary>
/// Gets or sets the search-engine name
/// </summary>
public virtual string SeName { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the product allows customer reviews
/// </summary>
public virtual bool AllowCustomerReviews { get; set; }
/// <summary>
/// Gets or sets the rating sum (approved reviews)
/// </summary>
public virtual int ApprovedRatingSum { get; set; }
/// <summary>
/// Gets or sets the rating sum (not approved reviews)
/// </summary>
public virtual int NotApprovedRatingSum { get; set; }
/// <summary>
/// Gets or sets the total rating votes (approved reviews)
/// </summary>
public virtual int ApprovedTotalReviews { get; set; }
/// <summary>
/// Gets or sets the total rating votes (not approved reviews)
/// </summary>
public virtual int NotApprovedTotalReviews { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity is published
/// </summary>
public virtual bool Published { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity has been deleted
/// </summary>
public virtual bool Deleted { get; set; }
/// <summary>
/// Gets or sets the date and time of product creation
/// </summary>
public virtual DateTime CreatedOnUtc { get; set; }
/// <summary>
/// Gets or sets the date and time of product update
/// </summary>
public virtual DateTime UpdatedOnUtc { get; set; }
/// <summary>
/// Gets or sets the collection of ProductCategory
/// </summary>
public virtual ICollection<ProductCategoryDto> ProductCategories
{
get { return _productCategories ?? (_productCategories = new List<ProductCategoryDto>()); }
protected set { _productCategories = value; }
}
}
}
ProductCategory Class
public partial class ProductCategory : BaseEntity
{
public virtual int ProductId { get; set; }
/// <summary>
/// Gets or sets the category identifier
/// </summary>
public virtual int CategoryId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the product is featured
/// </summary>
public virtual bool IsFeaturedProduct { get; set; }
/// <summary>
/// Gets or sets the display order
/// </summary>
public virtual int DisplayOrder { get; set; }
/// <summary>
/// Gets the category
/// </summary>
public virtual Category Category { get; set; }
/// <summary>
/// Gets the product
/// </summary>
public virtual Product Product { get; set; }
}
And ProductCategoryDto Class
public class ProductCategoryDto
{
/// <summary>
/// Gets or sets the product identifier
/// </summary>
public virtual int ProductId { get; set; }
/// <summary>
/// Gets or sets the category identifier
/// </summary>
public virtual int CategoryId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the product is featured
/// </summary>
public virtual bool IsFeaturedProduct { get; set; }
/// <summary>
/// Gets or sets the display order
/// </summary>
public virtual int DisplayOrder { get; set; }
/// <summary>
/// Gets the category
/// </summary>
public virtual CategoryDto Category { get; set; }
/// <summary>
/// Gets the product
/// </summary>
public virtual ProductDto Product { get; set; }
}
Mapping is done inside a method like this
public List<ProductDto> GetProductCollection(string usernameOrEmail, string userPassword)
{
CheckAccess(usernameOrEmail, userPassword);
if (!_permissionSettings.Authorize(StandardPermissionProvider.ManageCatalog))
throw new ApplicationException("Not allowed to manage Catalog");
List<Product> productslist = new List<Product>();
productslist.AddRange(_productService.GetAllProducts(false));
List<ProductCategory> productCategorylist = new List<ProductCategory>();
foreach (var product in productslist)
{
productCategorylist.AddRange(_categoryService.GetProductCategoriesByProductId(product.Id, false));
}
Mapper.CreateMap<Product, ProductDto>();
Mapper.CreateMap<ProductCategory, ProductCategoryDto>();
List<ProductDto> productsDto = Mapper.Map<List<Product>, List<ProductDto>>(productslist);
List<ProductCategoryDto> productCategoriesDto = Mapper.Map<List<ProductCategory>, List<ProductCategoryDto>>(productCategorylist);
return productsDto;
}
The problem is that I never get the mapping from ProductDto to the Icollection Of ProductCategoriesDto. Everything else works fine.
There is a custom data type for business object properties in one of my projects. This custom type is a wrapper for the basic Data Types in .NET.
When I try and get the value from the property, the below is displayed if the syntax is:
company.Name
Interfaces.Model.CustomType`1[System.String]
It is expecting:
company.Name.Value
I would like to avoid the need to use the .value; am I looking to overload an operation, or implicit/explicit methods?
Any help would be great.
Here is the general outline of the CustomType:
public class CustomType<t>
{
#region Implicit Casting
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static implicit operator t(CustomType<t> obj)
{
if (obj == null)
return new CustomType<t>().Value;
return obj.Value;
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static implicit operator CustomType<t>(t obj)
{
return new CustomType<t>(obj);
}
#endregion Implicit Casting
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>The value.</value>
public t Value
{
get
{
return _value;
}
set
{
_value = value;
}
}
/// <summary>
/// Sets the value.
/// </summary>
/// <param name="value">The value.</param>
/// <returns></returns>
public CustomType<t> setValue(t value)
{
try
{
Value = value;
}
catch (InvalidCastException e)
{
throw new InvalidCastException("CustomType invalid property cast ", e.InnerException);
}
return this;
}
}
If I understand correctly you need to override ToString.
public class CustomType<T>
{
public override string ToString()
{
return Value.ToString(); //I assume Value is of type T.
}
}
I've done a certain amount of guessing here, perhaps you could show the code for your custom type and the all that's giving you the type.
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.
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)
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);
}
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
});
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()
}
}
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.
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;
}
}
}