Among the two approaches which one is preferred with run with elevated privileges?
First Approach:
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite curSite = new SPSite(SPContext.Current.Site.ID))
{
using (SPWeb web = curSite.OpenWeb(SPContext.Current.Web.ID))
{
try
{
web.AllowUnsafeUpdates = true;
\\ do your stuff
}
catch (Exception e)
{
}
finally
{
web.AllowUnsafeUpdates = false;
web.Dispose();
}
}
}
});
Second Approach:
SPSite oSite = SPContext.Current.Site;
SPWeb oWeb = SPContext.Current.Web;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite curSite = new SPSite(oSite.ID))
{
using (SPWeb web = curSite.OpenWeb(oWeb.ID))
{
try
{
web.AllowUnsafeUpdates = true;
\\ do your stuff
}
catch (Exception e)
{
}
finally
{
web.AllowUnsafeUpdates = false;
web.Dispose();
oWeb.Dispose();
oSite.Dispose();
}
}
}
});
Are any of them suspected to throw "The web being updated was changed by an external process" exception ?
First of all, you should make a call to SPWeb.ValidateFormDigest() or SPUtility.ValidateFormDigest() before elevating the code. This will get rid of unsafe update not allowed with GET request, and avoid you to setup the AllowUnsafeUpdate property.
Secondly, as Nigel Whatling mentioned, you are disposing context object in your second code. You don't have to dispose them. To be simple, only dispose object you are instantiating yourself. A code like yours can cause side effects, as other SharePoint component may require access to SPContext.Current.XX objects. This is probably the root of your issue.
Thirdly, as you are using the using construct, you don't have to call .Dispose() on the variable you set up in the using header. Actually, the role (and actually benefit) of the using construct is that you don't have to care to dispose the object. As soon as the block code exits, even if there was an exception, the .Dispose() method is called on your object.
To conclude, your code should be changed to that :
SPUtility.ValidateFormDigest();
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite curSite = new SPSite(SPContext.Current.Site.ID))
{
using (SPWeb web = curSite.OpenWeb(SPContext.Current.Web.ID))
{
// Do stuff here
}
}
});
A side note: to elevate a code, you have two options. The one you use here (call to SPSecurity.RunWithElevatedPrivileges) or instantiating a new SPSite with the SystemAccount token :
using (SPSite curSite = new SPSite(
SPContext.Current.Site.ID,
SPContext.Current.Site.SystemAccount.UserToken
))
{
using (SPWeb web = curSite.OpenWeb(SPContext.Current.Web.ID))
{
// Do stuff here
}
}
This will allow you to run elevated code outside a webapplication.
You should also consider using some utility code to wrap such operations in a more functional way. I'm used to use code like this :
public static void RunWithElevatedPrivileges(this SPWeb web, Action<SPSite, SPWeb> codeToRunElevated)
{
if (CheckIfElevated(web))
{
codeToRunElevated(web.Site, web);
}
else
{
using (var elevatedSite = new SPSite(web.Site.ID, web.AllUsers["SHAREPOINT\\system"].UserToken))
{
using (var elevatedWeb = elevatedSite.OpenWeb(web.ID))
{
codeToRunElevated(elevatedSite, elevatedWeb);
}
}
}
}
/// <summary>
/// Indicates whether the context has been elevated
/// </summary>
public static bool CheckIfElevated(SPWeb web)
{
return web.CurrentUser.LoginName == "SHAREPOINT\\system";
}
Using such code, you can simply do, somewhere in your code :
SPContext.Current.Web.RunWithElevatedPrivileges((elevatedSite, elevatedWeb)=>{
// do something will all privileges
});
Both approaches are almost exactly identical. Except in the second approach you are also disposing the SPWeb and SPSite objects of the current context - something you should not do. Does the exception get thrown on a web.Update() call? Is the problem somewhere in the 'do your stuff' code?
Related
I am getting following error when accessing HttpContext.Session from static method placed in separate task:
Session has not been configured for this application or request.
I used this article to implement access to HttpContext outside the controller
From controller I invoke this static method that used to retrieve image data:
public static void CreateDummyGallery(Gallery gallery)
{
Logger.LogDebug(LogModule.Dummy, $"Starting gallery creation.");
Task.Factory.StartNew(() =>
{
try
{
List<DummyPicture> pictures;
using (var context = new MyzeumContext())
{
int top = 10;
pictures = context.DummyPictures.FromSql($"SELECT * FROM dummypictures ORDER BY RAND() LIMIT {top}").ToList();
}
Logger.LogDebug(LogModule.Dummy, $"Starting retrieving images.");
Parallel.ForEach(pictures, picture => {
using (WebClient client = new WebClient())
{
}
});
Logger.LogDebug(LogModule.Dummy, $"Done retrieving images.");
}
catch(Exception e)
{
Logger.LogError(LogModule.Server, e.Message, e);
}
});
}
The problem occurs in Logger.LogDebug() because this is where I access HttpContext:
public void LogDebug(LogModule module, string message, Exception stackTrace = null)
{
Log record = new Log();
record.Module = module;
record.ThreadId = Environment.CurrentManagedThreadId;
record.SessionId = HttpContextHelper.Current?.Session?.Id;
record.Message = message;
record.Logged = DateTime.UtcNow;
if(stackTrace != null)
{
record.Message += $" :{stackTrace.StackTrace}";
}
queue.Enqueue(record);
}
The problem 99% occurs in the first call inside task:
Logger.LogDebug(LogModule.Dummy, $"Starting retrieving images.");
BUT, right after application starts this whole task block works fine and does not throw any exception. Problem starts after following requests.
To begin with, I checked the discussions regarding this issue and couldn't find an answer to my problem and that's why I'm opening this question.
I've set up a web service using restlet 2.0.15.The implementation is only for the server. The connections to the server are made through a webpage, and therefore I didn't use ClientResource.
Most of the answers to the exhaustion of the thread pool problem suggested the inclusion of
#exhaust + #release
The process of web service can be described as a single function.Receive GET requests from the webpage, query the database, frame the results in XML and return the final representation. I used a Filter to override the beforeHandle and afterHandle.
The code for component creation code:
Component component = new Component();
component.getServers().add(Protocol.HTTP, 8188);
component.getContext().getParameters().add("maxThreads", "512");
component.getContext().getParameters().add("minThreads", "100");
component.getContext().getParameters().add("lowThreads", "145");
component.getContext().getParameters().add("maxQueued", "100");
component.getContext().getParameters().add("maxTotalConnections", "100");
component.getContext().getParameters().add("maxIoIdleTimeMs", "100");
component.getDefaultHost().attach("/orcamento2013", new ServerApp());
component.start();
The parameters are the result of a discussion present in this forum and modification by my part in an attempt to maximize efficiency.
Coming to the Application, the code is as follows:
#Override
public synchronized Restlet createInboundRoot() {
// Create a router Restlet that routes each call to a
// new instance of HelloWorldResource.
Router router = new Router(getContext());
// Defines only one route
router.attach("/{taxes}", ServerImpl.class);
//router.attach("/acores/{taxes}", ServerImplAcores.class);
System.out.println(router.getRoutes().size());
OriginFilter originFilter = new OriginFilter(getContext());
originFilter.setNext(router);
return originFilter;
}
I used an example Filter found in a discussion here, too. The implementation is as follows:
public OriginFilter(Context context) {
super(context);
}
#Override
protected int beforeHandle(Request request, Response response) {
if (Method.OPTIONS.equals(request.getMethod())) {
Form requestHeaders = (Form) request.getAttributes().get("org.restlet.http.headers");
String origin = requestHeaders.getFirstValue("Origin", true);
Form responseHeaders = (Form) response.getAttributes().get("org.restlet.http.headers");
if (responseHeaders == null) {
responseHeaders = new Form();
response.getAttributes().put("org.restlet.http.headers", responseHeaders);
responseHeaders.add("Access-Control-Allow-Origin", origin);
responseHeaders.add("Access-Control-Allow-Methods", "GET,POST,DELETE");
responseHeaders.add("Access-Control-Allow-Headers", "Content-Type");
responseHeaders.add("Access-Control-Allow-Credentials", "true");
response.setEntity(new EmptyRepresentation());
return SKIP;
}
}
return super.beforeHandle(request, response);
}
#Override
protected void afterHandle(Request request, Response response) {
if (!Method.OPTIONS.equals(request.getMethod())) {
Form requestHeaders = (Form) request.getAttributes().get("org.restlet.http.headers");
String origin = requestHeaders.getFirstValue("Origin", true);
Form responseHeaders = (Form) response.getAttributes().get("org.restlet.http.headers");
if (responseHeaders == null) {
responseHeaders = new Form();
response.getAttributes().put("org.restlet.http.headers", responseHeaders);
responseHeaders.add("Access-Control-Allow-Origin", origin);
responseHeaders.add("Access-Control-Allow-Methods", "GET,POST,DELETE"); //
responseHeaders.add("Access-Control-Allow-Headers", "Content-Type");
responseHeaders.add("Access-Control-Allow-Credentials", "true");
}
}
super.afterHandle(request, response);
Representation requestRepresentation = request.getEntity();
if (requestRepresentation != null) {
try {
requestRepresentation.exhaust();
} catch (IOException e) {
// handle exception
}
requestRepresentation.release();
}
Representation responseRepresentation = response.getEntity();
if(responseRepresentation != null) {
try {
responseRepresentation.exhaust();
} catch (IOException ex) {
Logger.getLogger(OriginFilter.class.getName()).log(Level.SEVERE, null, ex);
} finally {
}
}
}
The responseRepresentation does not have a #release method because it crashes the processes giving the warning WARNING: A response with a 200 (Ok) status should have an entity (...)
The code of the ServerResource implementation is the following:
public class ServerImpl extends ServerResource {
String itemName;
#Override
protected void doInit() throws ResourceException {
this.itemName = (String) getRequest().getAttributes().get("taxes");
}
#Get("xml")
public Representation makeItWork() throws SAXException, IOException {
DomRepresentation representation = new DomRepresentation(MediaType.TEXT_XML);
DAL dal = new DAL();
String ip = getRequest().getCurrent().getClientInfo().getAddress();
System.out.println(itemName);
double tax = Double.parseDouble(itemName);
Document myXML = Auxiliar.getMyXML(tax, dal, ip);
myXML.normalizeDocument();
representation.setDocument(myXML);
return representation;
}
#Override
protected void doRelease() throws ResourceException {
super.doRelease();
}
}
I've tried the solutions provided in other threads but none of them seem to work. Firstly, it does not seem that the thread pool is augmented with the parameters set as the warnings state that the thread pool available is 10. As mentioned before, the increase of the maxThreads value only seems to postpone the result.
Example: INFO: Worker service tasks: 0 queued, 10 active, 17 completed, 27 scheduled.
There could be some error concerning the Restlet version, but I downloaded the stable version to verify this was not the issue.The Web Service is having around 5000 requests per day, which is not much.Note: the insertion of the #release method either in the ServerResource or OriginFilter returns error and the referred warning ("WARNING: A response with a 200 (Ok) status should have an entity (...)")
Please guide.
Thanks!
By reading this site the problem residing in the server-side that I described was resolved by upgrading the Restlet distribution to the 2.1 version.
You will need to alter some code. You should consult the respective migration guide.
I am trying out the WCF Transaction implementation and I come up with the idea that whether asynchronous transaction is supported by WCF 4.0.
for example,
I have several service operations with client\service transaction enabled, in the client side, I use a TransactionScope and within the transaction, I create Tasks to asynchronously call those operations.
In this situation, I am assuming that the transaction is going to work correctly, is that right?
I doubt that very much. It appears that you if you are starting an ascync operation you are no longer participating on the original transaction.
I wrote a little LINQPad test
void Main()
{
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
try
{
Transaction.Current.Dump("created");
Task.Factory.StartNew(Test);
scope.Complete();
}
catch (Exception e)
{
Console.WriteLine(e);
}
Thread.Sleep(1000);
}
Console.WriteLine("closed");
Thread.Sleep(5000);
}
public void Test()
{
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
Transaction.Current.Dump("test start"); // null
Thread.Sleep(5000);
Console.WriteLine("done");
Transaction.Current.Dump("test end"); // null
}
}
You'll need to set both the OperationContext and Transaction.Current in the created Task.
More specifically, in the service you'll need to do like this:
public Task ServiceMethod() {
OperationContext context = OperationContext.Current;
Transaction transaction = Transaction.Current;
return Task.Factory.StartNew(() => {
OperationContext.Current = context;
Transaction.Current = transaction;
// your code, doing awesome stuff
}
}
This gets repetitive as you might suspect, so I'd recommend writing a helper for it.
I have a WCF service that is processing a call, sending that processed data onto another service, and alerting the caller and any other instances of that application by firing a callback. Originally the callbacks were being called at the end but I found that if the second service was not running that there would be a twenty second delay while we attempted to discover it. Only then were the callbacks called. I moved the callback notification before the call to the second service but it still had the delay. I even tried firing the callbacks on a background process but that didn't work either. Is there a way to get around this delay, outside of changing the timeout of the discovery? Here is a code snippet.
// Alert the admins of the change.
if (alertPuis)
{
ReportBoxUpdated(data.SerialNumber);
}
// Now send the change to the box if he's online.
var scope = new Uri(string.Format(#"net.tcp://{0}", data.SerialNumber));
var boxAddress = DiscoveryHelper.DiscoverAddress<IAtcBoxService>(scope);
if (boxAddress != null)
{
var proxy = GetBoxServiceProxy(boxAddress);
if (proxy != null)
{
proxy.UpdateBox(boxData);
}
else
{
Log.Write("AtcSystemService failed on call to update toool Box: {0}",
data.SerialNumber);
}
}
else if (mDal.IsBoxDataInPendingUpdates(data.SerialNumber) == false)
mDal.AddPendingUpdate(data.SerialNumber, null, true, null);
}
and
private static void ReportBoxUpdated(string serialNumber)
{
var badCallbacks = new List<string>();
Action<IAtcSystemServiceCallback> invoke = callback =>
callback.OnBoxUpdated(serialNumber);
foreach (var theCallback in AdminCallbacks)
{
var callback = theCallback.Value as IAtcSystemServiceCallback;
try
{
invoke(callback);
}
catch (Exception ex)
{
Log.Write("Failed to execute callback for admin instance {0}: {1}",
theCallback.Key, ex.Message);
badCallbacks.Add(theCallback.Key);
}
}
foreach (var bad in badCallbacks) // Clean out any stale callbacks from the list.
{
AdminCallbacks.Remove(bad);
}
}
Have you considered caching the result?
Hi
I am trying to create Site collection under web application which is configured as Claim based authentication and the code is as follow:
SPSecurity.RunWithElevatedPrivileges(delegate {
using (SPSite site = SPContext.Current.Site)
{
using (SPWeb web = site.RootWeb)
{
site.AllowUnsafeUpdates = true;
web.AllowUnsafeUpdates = true;
try
{
SPWebApplication web_App = web.Site.WebApplication;
web_App.Sites.Add(SiteUrl, SiteTitle, Description, Convert.ToUInt32(Constants.LOCALE_ID_ENGLISH), SiteTemplate, OwnerLogin, "testuser", OwnerEmail);
}
catch (Exception ex)
{
string s = ex.Message + " " + ex.StackTrace;
throw;
}
finally
{
web.AllowUnsafeUpdates = false;
site.AllowUnsafeUpdates = false;
}
}
}
});
Here I am passing "OwnerLogin" as "CustomMembership:UserName". But web_App.Sites.Add is throwing a wierd error like "ex = {Unable to evaluate expression because the code is optimized or a native frame is on top of the call stack.}". Any help in this regard is really appreciated.
Regards,
Paddy
Your elevation code is wrong, you need to create completely new SPSite and SPWeb references. I normal prefix them with "c" to show me that it is in a different context.
SPSecurity.RunWithElevatedPrivileges(delegate() {
using (SPSite csite = new SPSite(SPContext.Current.Site.ID)) {
using (SPWeb cweb = csite.OpenWeb(SPContext.Current.Site.RootWeb.ID)) {
//do stuff
}
}
});
The OwnerLogin parameter should not contain the CustomMembership: prefix - pass plain UserName as a value of this parameter.
By the way, your method of getting the Web Application object is unnecessarily complicated - use something like this:
SPWebApplication webApplication = SPWebApplication.Lookup(new System.Uri("Web-Application-URL"));