I am associating ribbon to my webpart. I have a need to add more than two webparts in a page.
I do not want to have a separate contextual group/tab for each webpart. Is there a way to check if a specific group/tab exists in the current ribbon on the page?
At this point, when I add more than one webpart to the page, I an getting the following error:
Item has already been added. Key in dictionary: 'Ribbon.MyContextualTabGroup' Key being added: 'Ribbon.MyContextualTabGroup'
Here is my code for your reference:
/// <summary>
/// Gets the web part contextual info.
/// </summary>
public WebPartContextualInfo WebPartContextualInfo
{
get
{
var webPartContextualInfo = new WebPartContextualInfo();
var webPartRibbonContextualGroup = new WebPartRibbonContextualGroup();
var webPartRibbonTab = new WebPartRibbonTab();
webPartRibbonContextualGroup.Id = "Ribbon.MyContextualTabGroup";
webPartRibbonContextualGroup.Command = "MyContextualTab.EnableContextualGroup";
webPartRibbonContextualGroup.VisibilityContext = "MyContextualTab.CustomVisibilityContext";
webPartRibbonTab.Id = "Ribbon.MyTab";
webPartRibbonTab.VisibilityContext = "MyContextualTab.CustomVisibilityContext";
webPartContextualInfo.ContextualGroups.Add(webPartRibbonContextualGroup);
webPartContextualInfo.Tabs.Add(webPartRibbonTab);
webPartContextualInfo.PageComponentId = SPRibbon.GetWebPartPageComponentId(this);
return webPartContextualInfo;
}
}
/// <summary>
/// Adds the contextual tab.
/// </summary>
private void AddContextualTab()
{
SPRibbon spRibbon = SPRibbon.GetCurrent(Page);
if (spRibbon == null) return;
var ribbonExtensions = new XmlDocument();
ribbonExtensions.LoadXml(_contextualTab);
spRibbon.RegisterDataExtension(ribbonExtensions.FirstChild, "Ribbon.ContextualTabs._children");
ribbonExtensions.LoadXml(_contextualTabTemplate);
spRibbon.RegisterDataExtension(ribbonExtensions.FirstChild, "Ribbon.Templates._children");
}
/// <summary>
/// The event handler for the System.Web.UI.Control.PreRender event that occurs immediately before the Web Part is rendered to the Web Part Page it is contained on.
/// </summary>
/// <param name="e">A System.EventArgs that contains the event data.</param>
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
AddContextualTab();
ClientScriptManager clientScriptManager = Page.ClientScript;
clientScriptManager.RegisterClientScriptBlock(GetType(), "MyWebPart", DelayScript);
}
Contextual ribbons cannot be shared between different instances of a web part. Since the ribbon will only be displayed if your web part instance has the "focus" on the page. Therefore several instances of the web part have to create their own contextual group.
To avoid ribbon ID duplication append a web part instance specific part to the ribbon IDs. You could use the web part's ID:
webPartRibbonContextualGroup.Id = "Ribbon.MyContextualTabGroup." + ID;
// ...
webPartRibbonTab.Id = "Ribbon.MyTab." + ID;
// etc.
Related
We're implementing a new web application in Asp.net core 2.0, and I'd like to be able to restrict actions based on a combination of things, rather than one particular user role (like admin, power user, etc). The problem space looks like this:
Each User can have a particular 'home' facility that they have default permissions at based on their job function.
CRUD actions each have a particular permission associated with them.
Each permission can be granted at any number of facilities, just one, or none at all (or some combination thereof).
A particular user could have different permissions at different facilities. For example, a regional manager could have View and Order permissions at all of the facilities they work with, but only have the View permission to facilities in neighboring regions.
Currently, we use a home-grown solution that's getting out of hand to limit permissions, but it only works with a users 'home' facility. We can't grant someone that orders inventory from another facility, for example, to view a different facility's inventory.
We've attempted to just apply roles for each action in each facility (Yikes!) that are generated on the fly, but this lead to some users getting permissions they shouldn't have. Not to mention, its a nightmare to maintain.
How can I extend the Roles Functionality in ASP.NET Core 2.0 to allow my users to have different permissions in different facilities without having to create roles for each action at each facility?
I'd recommend using policies. They give you much finer-grained control. Basically, you start with one or more "requirements". For example, you might start with something like:
public class ViewFacilitiesRequirement : IAuthorizationRequirement
{
}
public class OrderFacilitiesRequirement : IAuthorizationRequirement
{
}
These mostly function as attachments for authorization handlers, so they're pretty basic. The meat comes in those authorization handlers, where you define what meeting the requirement actually means.
public class ViewFacilitiesHandler : AuthorizationHandler<ViewFacilitiesRequirement>
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ViewFacilitiesRequirement requirement)
{
// logic here, if user is authorized:
context.Succeed(requirement);
}
}
Authorization handlers are dependency injected, so you can inject things like your DbContext, UserManager<TUser>, etc. into them in the normal way and then query those sources to determine whether or not the user is authorized.
Once you've got some requirements and handlers, you need to register them:
services.AddAuthorization(o =>
{
o.AddPolicy("ViewFacilities", p =>
p.Requirements.Add(new ViewFacilitiesRequirement()));
});
services.AddScoped<IAuthorizationHandler, ViewFacilitiesHandler>();
In case it's not obvious, a policy can utilize multiple requirements. All will have to pass for the policy to pass. The handlers just need to be registered with the DI container. They are applied automatically based on the type(s) of requirements they apply to.
Then, on the controller or action that needs this permission:
[Authorize(Policy = "ViewFacilities")]
This is a very basic example, of course. You can make handlers than can work with multiple different requirements. You can build out your requirements a bit more, so you don't need as many of those either. Or you may prefer to be more explicit, and have requirements/handlers for each specific scenario. It's entirely up to you.
For more detail, see the documentation.
You could create an AuthorizationFilterAttribute and assign it to each API endpoint or Controller class. This will allow you to assign case-by-case permissions to each of your users, then you just need a table containing specific permission IDs.
Here's an implementation that pulls username from basic authentication. You can change authentication to your implementation by changing the OnAuthorization method to retrieve user details from wherever you store it.
/// <summary>
/// Generic Basic Authentication filter that checks if user is allowed
/// to access a specific permssion.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class BasicAuthenticationFilter: AuthorizationFilterAttribute
{
private readonly bool _active = true;
private readonly int _permissionId;
public BasicAuthenticationFilter(int permissionId)
{
private _permissionId = permissionId;
}
/// <summary>
/// Overriden constructor to allow explicit disabling of this
/// filter's behavior. Pass false to disable (same as no filter
/// but declarative)
/// </summary>
public BasicAuthenticationFilter(bool active) => _active = active;
/// <summary>
/// Override to Web API filter method to handle Basic Authentication.
/// </summary>
public override void OnAuthorization(HttpActionContext actionContext)
{
if (!_active) return;
BasicAuthenticationIdentity identity = ParseAuthorizationHeader(actionContext);
if (identity == null && !OnAuthorizeUser(identity.Name, identity.Password, actionContext))
{
Challenge(actionContext);
return;
}
Thread.CurrentPrincipal = new GenericPrincipal(identity, null);
base.OnAuthorization(actionContext);
}
/// <summary>
/// Base implementation for user authentication you'll want to override this implementing
/// requirements on a case-by-case basis.
/// </summary>
protected virtual bool OnAuthorizeUser(string username, string password, HttpActionContext actionContext)
{
if (!Authorizer.Validate(username,password)) // check if user is authentic
return false;
using (var db = new DbContext())
{
var userPermissions = _context.UserPermissions
.Where(user => user.UserName == username);
if (userPermissions.Permission.Contains(_permissionId))
return true;
else
return false;
return true;
}
}
/// <summary>
/// Parses the Authorization header and creates user credentials
/// </summary>
protected virtual BasicAuthenticationIdentity ParseAuthorizationHeader(HttpActionContext actionContext)
{
string authHeader = null;
System.Net.Http.Headers.AuthenticationHeaderValue auth = actionContext.Request.Headers.Authorization;
if (auth?.Scheme == "Basic")
authHeader = auth.Parameter;
if (String.IsNullOrEmpty(authHeader))
return null;
authHeader = Encoding.Default.GetString(Convert.FromBase64String(authHeader));
string[] tokens = authHeader.Split(':');
if (tokens.Length < 2)
return null;
return new BasicAuthenticationIdentity(tokens[0], tokens[1]);
}
/// <summary>
/// Send the Authentication Challenge request
/// </summary>
private void Challenge(HttpActionContext actionContext)
{
var host = actionContext.Request.RequestUri.DnsSafeHost;
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", host));
}
}
Then you just add the filter to your API methods:
private const int _orderPermission = 450;
/// <summary>
/// Submit a new order.
/// </summary>
[HttpPost, BasicAuthenticationFilter(_orderPermission)]
public void Submit([FromBody]OrderData value)
{
Task.Run(()=> ProcessInbound());
return Ok();
}
I'm attempting to be able to replace an attribute in CRM with another attribute. Currently they are both option sets, but I need to be able to do this programatically. I am able to download the forms for the entity, search for the attribute, and replace it with a different one, but I'm unsure how to do this in Workflows/Dialogs. Anyone have any idea how to approach it? Anyone have a working code example?
Here is a sample of updating Dialog's Taken from the SDK:
/// <summary>
/// Demonstrates how to create, retrieve, update, and delete.
/// a dialog process.</summary>
/// <remarks>
/// At run-time, you will be given the option to delete all the
/// database records created by this program.</remarks>
public class CRUDDialog
{
#region Class Level Members
private Guid _dialogId;
private OrganizationServiceProxy _serviceProxy;
/// <summary>
/// TODO: Change the location and file name of the sample XAML file
/// containing the dialog definition.
/// e.g. Use the sample xml file located in the SDK\SampleCode\CS\Dialogs folder.
/// </summary>
String pathToXAML = Path.Combine(Environment.CurrentDirectory, #"CallCategorization.xml");
#endregion Class Level Members
#region How-To Sample Code
/// <summary>
/// This method first connects to the Organization service. Afterwards,
/// create, retrieve, update, and delete operations are performed on a
/// dialog process.
/// </summary>
/// <param name="serverConfig">Contains server connection information.</param>
/// <param name="promptforDelete">When True, the user will be prompted to delete all
/// created entities.</param>
public void Run(ServerConnection.Configuration serverConfig, bool promptforDelete)
{
try
{
// Connect to the Organization service.
// The using statement assures that the service proxy will be properly disposed.
using (_serviceProxy = new OrganizationServiceProxy(serverConfig.OrganizationUri, serverConfig.HomeRealmUri,serverConfig.Credentials, serverConfig.Device
{
// This statement is required to enable early-bound type support.
_serviceProxy.EnableProxyTypes();
CreateRequiredRecords();
// Define an anonymous type to define the possible values for
// workflow category
var WorkflowCategory = new
{
Workflow = 0,
Dialog = 1
};
// Instantiate a Workflow object.
// See the Entity Metadata topic in the SDK documentation to determine
// which attributes must be set for each entity.
Workflow sampleDialog = new Workflow
{
Category = new OptionSetValue((int)WorkflowCategory.Dialog),
Name = "Sample Dialog: Call Categorization",
PrimaryEntity = PhoneCall.EntityLogicalName,
//Language code for U.S. English
LanguageCode = 1033,
Xaml = File.ReadAllText(pathToXAML)
};
// Create a dialog record.
_dialogId = _serviceProxy.Create(sampleDialog);
Console.Write("{0} created,", sampleDialog.Name);
// Activate the dialog.
SetStateRequest activateRequest = new SetStateRequest
{
EntityMoniker = new EntityReference(Workflow.EntityLogicalName, _dialogId),
State = new OptionSetValue((int)WorkflowState.Activated),
Status = new OptionSetValue(2)
};
_serviceProxy.Execute(activateRequest);
Console.WriteLine(" and activated.");
// Retrieve the dialog containing several of its attributes.
ColumnSet cols = new ColumnSet("name", "statecode", "statuscode");
Workflow retrievedDialog = (Workflow)_serviceProxy.Retrieve(Workflow.EntityLogicalName, _dialogId, cols);
Console.Write("Retrieved,");
// Update the dialog.
// Deactivate the dialog before you can update it.
SetStateRequest deactivateRequest = new SetStateRequest
{
EntityMoniker = new EntityReference(Workflow.EntityLogicalName, _dialogId),
State = new OptionSetValue((int)WorkflowState.Draft),
Status = new OptionSetValue(1)
};
_serviceProxy.Execute(deactivateRequest);
// Retrieve the dialog record again to get the unpublished
// instance in order to update.
Workflow retrievedDialogDeactivated = (Workflow)_serviceProxy.Retrieve(Workflow.EntityLogicalName, _dialogId, cols);
// Update the dialog.
retrievedDialogDeactivated.Name = "Updated Dialog: Call Categorization";
_serviceProxy.Update(retrievedDialogDeactivated);
Console.Write(" updated,");
// Activate the dialog.
SetStateRequest updateActivateRequest = new SetStateRequest
{
EntityMoniker = new EntityReference(Workflow.EntityLogicalName, _dialogId),
State = new OptionSetValue((int)WorkflowState.Activated),
Status = new OptionSetValue(2)
};
_serviceProxy.Execute(updateActivateRequest);
Console.WriteLine(" and activated again.");
DeleteRequiredRecords(promptforDelete);
}
}
// Catch any service fault exceptions that Microsoft Dynamics CRM throws.
catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)
{
// You can handle an exception here or pass it back to the calling method.
throw;
}
}
}
Hope that helps.
Edit:
// Update the dialog.
// Deactivate the dialog before you can update it.
SetStateRequest deactivateRequest = new SetStateRequest
{
EntityMoniker = new EntityReference(Workflow.EntityLogicalName, _dialogId),
State = new OptionSetValue((int)WorkflowState.Draft),
Status = new OptionSetValue(1)
};
_serviceProxy.Execute(deactivateRequest);
// Retrieve the dialog record again to get the unpublished
// instance in order to update.
Workflow retrievedDialogDeactivated = (Workflow)_serviceProxy.Retrieve(Workflow.EntityLogicalName, _dialogId, cols);
// Update the dialog.
retrievedDialogDeactivated.Name = "Updated Dialog: Call Categorization";
_serviceProxy.Update(retrievedDialogDeactivated);
Console.Write(" updated,");
While it is just updating the name of the Dialog, I am sure that it can be applied to any of the parts of that Dialog.
I've got the following problem:
I created a WebPart with a ToolPart,
this toolpart has multiple controls (textbox, dropdownlist, ...)
when I fill in everything and apply, it all goes ok,
even when i press ok. But when i go back to
edit -> modify webpart, all my data i've entered is gone.
How can i solve this?
Thanks
You'll need to save the values from the Toolpart in the webpart's properties. For example, lets say I want to save a string for "Title"... in the webpart define a property:
private const string DEFAULT_WPPColumnTitle = "Title";
private string _WPPColumnTitle = DEFAULT_WPPColumnTitle;
[Browsable(false)]
[WebPartStorage(Storage.Shared)]
public string WPPColumnTitle
{
get { return this._WPPColumnTitle; }
set { this._WPPColumnTitle = value; }
}
I always use the prefix "WPP" to keep all the web part properties together.
Then, in the Toolpart's ApplyChanges override, save the control's value (_ddlColumnsTitle) to the webpart (WPPColumnTitle):
/// <summary>
/// Called by the tool pane to apply property changes to
/// the selected Web Part.
/// </summary>
public override void ApplyChanges()
{
// get our webpart and set it's properties
MyCustomWebPart et = (MyCustomWebPart)ParentToolPane.SelectedWebPart;
et.WPPColumnTitle = _ddlColumnsTitle.SelectedValue;
}
Lastly, if the user edited the properties already, we want the Toolpart to be pre-populated with the user's configuration. In the CreateChildControls() method of your Toolpart, initialize the controls:
protected override void CreateChildControls()
{
try
{
MyCustomWebPart et = (MyCustomWebPart)ParentToolPane.SelectedWebPart;
// ... code to create _ddlColumnsTitle and add it to the Controls
// default our dropdown to the user's selection
ListItem currentItem = _ddlColumnsTitle.Items.FindByValue(et.WPPColumnTitle);
if (null != currentItem)
{
_ddlColumnsTitle.SelectedValue = currentItem.Value;
}
}
catch (Exception ex)
{
_errorMessage = "Error adding edit controls. " + ex.ToString();
}
}
Open up the debugger and double check that the values are getting applied to your propertries on Apply (i.e. WPPColumnTitle is set).
If so then problem is that SharePoint is not serializing/deserializing the value from the property (WPPColumnTitle) to the database and back - verify by writing out this property on the web part - as soon as you leave the page and come back it will be empty.
If so then check things like this on class
[XmlRoot(Namespace = "YourNamespace")]
and this (not strictly necessary) on properties
[XmlElement(ElementName = "ColumnTitle")]
I've also seen problems if you name your web part class "WebPart" so call it MyWebPart
I've solved it with adding a property in my webpart "IsNeverSet" (bool)
and when i go to the "CreateControls()" of my toolpart, I get this property
and if it's false, I load all the properties from my webpart and fill them in the toolpart.
So I found it with the help of Kit Menke
I have a SharePoint workflow which is running whenever the item changes. The workflow communicates with an external REST service. If the service returns a string, I want to update one of the field values with that string. Unfortunately, this update will trigger another instance of the workflow for this item once the current workflow terminates. I end up with an infinite loop!
How I can prevent this from happening? SPListItem has Update(), UpdateOverwriteVersion(), and SystemUpdate() methods but none of them seem to prevent subsequent workflows from being triggered.
I could inspect the last modified timestamp of the item and terminate the workflow if the last update happened within a certain timespan, but I am looking for a more robust solution.
You could use some extension method to update item silently.
public static class SPListItemExtensions
{
/// <summary>
/// Provides ability to update list item without firing event receiver.
/// </summary>
/// <param name="item"></param>
/// <param name="doNotFireEvents">Disables firing event receiver while updating item.</param>
public static void Update(this SPListItem item, bool doNotFireEvents)
{
SPItemEventReceiverHandling rh = new SPItemEventReceiverHandling();
if (doNotFireEvents)
{
try
{
rh.DisableEventFiring();
item.Update();
}
finally
{
rh.EnableEventFiring();
}
}
else
{
item.Update();
}
}
/// <summary>
/// Provides ability to update list item without firing event receiver.
/// </summary>
/// <param name="item"></param>
/// <param name="incrementListItemVersion"></param>
/// <param name="doNotFireEvents">Disables firing event receiver while updating item.</param>
public static void SystemUpdate(this SPListItem item, bool incrementListItemVersion, bool doNotFireEvents)
{
SPItemEventReceiverHandling rh = new SPItemEventReceiverHandling();
if (doNotFireEvents)
{
try
{
rh.DisableEventFiring();
item.SystemUpdate(incrementListItemVersion);
}
finally
{
rh.EnableEventFiring();
}
}
else
{
item.SystemUpdate(incrementListItemVersion);
}
}
/// <summary>
/// Provides ability to update list item without firing event receiver.
/// </summary>
/// <param name="item"></param>
/// <param name="doNotFireEvents">Disables firing event receiver while updating item.</param>
public static void SystemUpdate(this SPListItem item, bool doNotFireEvents)
{
SPItemEventReceiverHandling rh = new SPItemEventReceiverHandling();
if (doNotFireEvents)
{
try
{
rh.DisableEventFiring();
item.SystemUpdate();
}
finally
{
rh.EnableEventFiring();
}
}
else
{
item.SystemUpdate();
}
}
private class SPItemEventReceiverHandling : SPItemEventReceiver
{
public SPItemEventReceiverHandling() { }
new public void DisableEventFiring()
{
base.DisableEventFiring();
}
new public void EnableEventFiring()
{
base.EnableEventFiring();
}
}
}
Some more links because the above solution is not working for 2010:
MSDN SPEventReceiverBase.EventFiringEnabled Property
How DisableEventFiring / EventFiringEnabled works
The best solution:
Disable Sharepoint item events firing during item update
It seems Microsoft have reworked this in SharePoint 2010, the EventFiringEnabled and EventFiringDisabled have been made obsolete.
Instead use a boolean Property named EventFiringEnabled.
Could you add a step to the beginning of the workflow that terminates the workflow if an update was triggered by a change to that field? (The one that is updated by using the service.)
You could have a hidden boolean field on the list item that you set to true when you update the list using the service. Then, at the beginning of the workflow, you could check to see if this field is set to true.
you can use set field in current item instud of update list item.
Set field update the list item with out triggering new event
I a console application that creates a sub sites under a site collection
The site collection accepts only forms based user.
Now, when i run the console application, its with windows credentials.
I need some way to run the code in console app that creates sub site to run under forms user who is admin to that site collection.
Please let me know your suggestions.
Thanks
You need to create a new Web service within the Central Administration Web application ([12 hive]\AMDISAPI) and add a function that creates subsites.
Here's an example - the hstCreateSubSite function from the SharePoint for Hosters project:
/// <summary>
/// Method to create a Sub site for a site collection
/// </summary>
/// <param name="strSiteURL">url of the sitecollection i.e. "http://www.sitea.com"</param>
/// <param name="subsitePath">the path to the subsite i.e. inventory</param>
/// <param name="strTitle">sub site title</param>
/// <param name="strDesc">sub site description</param>
/// <param name="strTemplate">a valid templateID</param>
/// <param name="nLCID">the LCID for the language i.e. 1033 for english</param>
[WebMethod]
public void hstCreateSubSite(string strSiteURL, string subSitePath, string strTitle, string strDesc, string strTemplate, uint nLCID)
{
SPSite oSite = new SPSite(strSiteURL);
SPWeb oSubSiteWeb = oSite.OpenWeb();
SPWeb oWeb = null;
if (String.IsNullOrEmpty(strDesc)) strDesc = null;
if (String.IsNullOrEmpty(strTitle)) strTitle = null;
try
{
// elevate permissions to allow user to create a new site.
SPSecurity.RunWithElevatedPrivileges(delegate()
{
// the subsite will inherit permissions and will not convert the site if it exists
oWeb = oSubSiteWeb.Webs.Add(subSitePath, strTitle, strDesc, nLCID, strTemplate, false, false);
SPNavigationNodeCollection nodes = oSubSiteWeb.Navigation.TopNavigationBar;
SPNavigationNode navNode = new SPNavigationNode(strTitle, subSitePath);
nodes.AddAsLast(navNode);
oWeb.Navigation.UseShared = true;
// create entry in property bag to store template and url in the subsite.
oWeb.AllowUnsafeUpdates = true;
// add the Templateid to the property bag. This needs to be done becuase
// sites that are created from site templates (.stp) do not retain the templateid.
oWeb.Properties.Add("STP_ID", strTemplate);
oWeb.Properties.Update();
oWeb.AllowUnsafeUpdates = false;
});
}
catch (Exception ex)
{
throw ex;
}
finally
{
//dispose objects
if (oWeb != null)
oWeb.Dispose();
if (oSite != null)
oSite.Dispose();
}
}