How can i force the DbUpdateConcurrencyException to be raised even if i am passing the FormCollection to my Post action method instead of an object - asp.net-mvc-3

i have the following action method inside my asp.net mvc web application , which will raise a DbUpdateConcurrencyException as intended to handle any concurrent conflicts that might happen:-
[HttpPost]
public ActionResult Edit(Assessment a)
{ try
{
if (ModelState.IsValid)
{
elearningrepository.UpdateAssessment(a);
elearningrepository.Save();
return RedirectToAction("Details", new { id = a.AssessmentID });
}
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var clientValues = (Assessment)entry.Entity;
ModelState.AddModelError(string.Empty, "The record you attempted to edit was"
+ "modified by another user after you got the original value.");
}
catch (DataException)
{
ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
}
return View(a);}
but to avoid any over binding attacks i have define a [Bind(Include = "Date, Title")] on the object class, but this raised a problem to me as the above action method will return an exception even if no concurrent conflict occur becuase the model binder will not be able to bind the object ID and other values ,, so i have changed my action method to the following:-
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
Assessment a = elearningrepository.GetAssessment(id);
try
{
if (TryUpdateModel(a))
{
elearningrepository.UpdateAssessment(a);
elearningrepository.Save();
return RedirectToAction("Details", new { id = a.AssessmentID });
}
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var clientValues = (Assessment)entry.Entity;
ModelState.AddModelError(string.Empty, "The record you attempted to edit was"
+ "modified by another user after you got the original value.");
}
catch (DataException)
{ ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
}return View(a);
but writting the action method as in the second approach will not raise the DbUpdateConcurrencyException under any situation (even if a concurrency conflict occurs!!!).
so me question is how i can make sure that the DbUpdateConcurrencyException will be raised if any conflict occur and at the same time to make sure that no over binding attack might occur by defining [Bind(Include = "Date, Title")]?
thanks in advance for any help and suggestions .
BR

Stop using forms collection and use a view model, thats a far better approach.
Also I have an action filter I wrote to handle the concurrency exceptions (MVC4 handles entity exceptions now finally has pat of validation just not the concurrency exceptions). Its a work in progress but should work ok as is, that much has been tested : )
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Data.Entity.Infrastructure;
using System.Reflection;
namespace Gecko.Framework.Mvc.ActionFilters
{
/// <summary>
/// Author: Adam Tuliper
/// adam.tuliper#gmail.com
/// completedevelopment.blogspot.com
/// www.secure-coding.com
/// Use freely, just please retain original credit.
///
/// This attribute attempts to intercept DbUpdateConcurrencyException to write out original/new values
/// to the screen for the user to review.
/// It assumes the following:
/// 1. There is a [Timestamp] attribute on an entity framework model property
/// 2. The only differences that we care about from the posted data to the record currently in the database are
/// only yhe model state field. We do not have access to a model at this point, as an exception was raised so there was no
/// return View(model) that we have a model to process from.
/// As such, we have to look at the fields in the modelstate and try to find matching fields on the entity and then display the differences.
/// This may not work in all cases.
/// This class will look at your model to get the property names. It will then check your
/// Entities current values vs. db values for these property names.
/// The behavior can be changed.
/// </summary>
public class HandleConcurrencyException : FilterAttribute, IExceptionFilter //ActionFilterAttribute
{
private PropertyMatchingMode _propertyMatchingMode;
/// <summary>
/// This defines when the concurrencyexception happens,
/// </summary>
public enum PropertyMatchingMode
{
/// <summary>
/// Uses only the field names in the model to check against the entity. This option is best when you are using
/// View Models with limited fields as opposed to an entity that has many fields. The ViewModel (or model) field names will
/// be used to check current posted values vs. db values on the entity itself.
/// </summary>
UseViewModelNamesToCheckEntity = 0,
/// <summary>
/// Use any non-matching value fields on the entity (except timestamp fields) to add errors to the ModelState.
/// </summary>
UseEntityFieldsOnly = 1,
/// <summary>
/// Tells the filter to not attempt to add field differences to the model state.
/// This means the end user will not see the specifics of which fields caused issues
/// </summary>
DontDisplayFieldClashes = 2
}
/// <summary>
/// The main method, called by the mvc runtime when an exception has occured.
/// This must be added as a global filter, or as an attribute on a class or action method.
/// </summary>
/// <param name="filterContext"></param>
public void OnException(ExceptionContext filterContext)
{
if (!filterContext.ExceptionHandled && filterContext.Exception is DbUpdateConcurrencyException)
{
//Get original and current entity values
DbUpdateConcurrencyException ex = (DbUpdateConcurrencyException)filterContext.Exception;
var entry = ex.Entries.Single();
//problems with ef4.1/4.2 here because of context/model in different projects.
//var databaseValues = entry.CurrentValues.Clone().ToObject();
//var clientValues = entry.Entity;
//So - if using EF 4.1/4.2 you may use this workaround
var clientValues = entry.CurrentValues.Clone().ToObject();
entry.Reload();
var databaseValues = entry.CurrentValues.ToObject();
List<string> propertyNames;
filterContext.Controller.ViewData.ModelState.AddModelError(string.Empty, "The record you attempted to edit "
+ "was modified by another user after you got the original value. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again to cause your changes to be the current saved values.");
PropertyInfo[] entityFromDbProperties = databaseValues.GetType().GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance);
if (_propertyMatchingMode == PropertyMatchingMode.UseViewModelNamesToCheckEntity)
{
//We dont have access to the model here on an exception. Get the field names from modelstate:
propertyNames = filterContext.Controller.ViewData.ModelState.Keys.ToList();
}
else if (_propertyMatchingMode == PropertyMatchingMode.UseEntityFieldsOnly)
{
propertyNames = databaseValues.GetType().GetProperties(BindingFlags.Public).Select(o => o.Name).ToList();
}
else
{
filterContext.ExceptionHandled = true;
UpdateTimestampField(filterContext, entityFromDbProperties, databaseValues);
filterContext.Result = new ViewResult() { ViewData = filterContext.Controller.ViewData };
return;
}
UpdateTimestampField(filterContext, entityFromDbProperties, databaseValues);
//Get all public properties of the entity that have names matching those in our modelstate.
foreach (var propertyInfo in entityFromDbProperties)
{
//If this value is not in the ModelState values, don't compare it as we don't want
//to attempt to emit model errors for fields that don't exist.
//Compare db value to the current value from the entity we posted.
if (propertyNames.Contains(propertyInfo.Name))
{
if (propertyInfo.GetValue(databaseValues, null) != propertyInfo.GetValue(clientValues, null))
{
var currentValue = propertyInfo.GetValue(databaseValues, null);
if (currentValue == null || string.IsNullOrEmpty(currentValue.ToString()))
{
currentValue = "Empty";
}
filterContext.Controller.ViewData.ModelState.AddModelError(propertyInfo.Name, "Current value: "
+ currentValue);
}
}
//TODO: hmm.... how can we only check values applicable to the model/modelstate rather than the entity we saved?
//The problem here is we may only have a few fields used in the viewmodel, but many in the entity
//so we could have a problem here with that.
}
filterContext.ExceptionHandled = true;
filterContext.Result = new ViewResult() { ViewData = filterContext.Controller.ViewData };
}
}
public HandleConcurrencyException()
{
_propertyMatchingMode = PropertyMatchingMode.UseViewModelNamesToCheckEntity;
}
public HandleConcurrencyException(PropertyMatchingMode propertyMatchingMode)
{
_propertyMatchingMode = propertyMatchingMode;
}
/// <summary>
/// Searches the database loaded entity values for a field that has a [Timestamp] attribute.
/// It then writes a string version of ther byte[] timestamp out to modelstate, assuming
/// we have a timestamp field on the page that caused the concurrency exception.
/// </summary>
/// <param name="filterContext"></param>
/// <param name="entityFromDbProperties"></param>
/// <param name="databaseValues"></param>
private void UpdateTimestampField(ExceptionContext filterContext, PropertyInfo[] entityFromDbProperties, object databaseValues)
{
foreach (var propertyInfo in entityFromDbProperties)
{
var attributes = propertyInfo.GetCustomAttributesData();
//If this is a timestamp field, we need to set the current value.
foreach (CustomAttributeData attr in attributes)
{
if (typeof(System.ComponentModel.DataAnnotations.TimestampAttribute).IsAssignableFrom(attr.Constructor.DeclaringType))
{
//This currently works only with byte[] timestamps. You can use dates as timestampts, but support is not provided here.
byte[] timestampValue = (byte[])propertyInfo.GetValue(databaseValues, null);
//we've found the timestamp. Add it to the model.
filterContext.Controller.ViewData.ModelState.Add(propertyInfo.Name, new ModelState());
filterContext.Controller.ViewData.ModelState.SetModelValue(propertyInfo.Name,
new ValueProviderResult(Convert.ToBase64String(timestampValue), Convert.ToBase64String(timestampValue), null));
break;
}
}
}
}
}
}

Do you know why I had to change
if (propertyInfo.GetValue(databaseValues, null) != propertyInfo.GetValue(clientValues, null))
to
if (!Equals(propertyInfo.GetValue(databaseValues,null), propertyInfo.GetValue(clientValues,null)))
It was giving me false positives for anything that was not a string.
After I made that change it worked for me.
Here is the complete modified function
public void OnException(ExceptionContext filterContext)
{
if (!filterContext.ExceptionHandled && filterContext.Exception is DbUpdateConcurrencyException)
{
//Get original and current entity values
DbUpdateConcurrencyException ex = (DbUpdateConcurrencyException)filterContext.Exception;
var entry = ex.Entries.Single();
//problems with ef4.1/4.2 here because of context/model in different projects.
//var databaseValues = entry.CurrentValues.Clone().ToObject();
//var clientValues = entry.Entity;
//So - if using EF 4.1/4.2 you may use this workaround
var clientValues = entry.CurrentValues.Clone().ToObject();
entry.Reload();
var databaseValues = entry.CurrentValues.ToObject();
List<string> propertyNames;
filterContext.Controller.ViewData.ModelState.AddModelError(string.Empty, "The record you attempted to edit "
+ "was modified by another user after you got the original value. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again to cause your changes to be the current saved values.");
PropertyInfo[] entityFromDbProperties = databaseValues.GetType().GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance);
if (_propertyMatchingMode == PropertyMatchingMode.UseViewModelNamesToCheckEntity)
{
//We dont have access to the model here on an exception. Get the field names from modelstate:
propertyNames = filterContext.Controller.ViewData.ModelState.Keys.ToList();
}
else if (_propertyMatchingMode == PropertyMatchingMode.UseEntityFieldsOnly)
{
propertyNames = databaseValues.GetType().GetProperties(BindingFlags.Public).Select(o => o.Name).ToList();
}
else
{
filterContext.ExceptionHandled = true;
UpdateTimestampField(filterContext, entityFromDbProperties, databaseValues);
filterContext.Result = new ViewResult() { ViewData = filterContext.Controller.ViewData };
return;
}
UpdateTimestampField(filterContext, entityFromDbProperties, databaseValues);
//Get all public properties of the entity that have names matching those in our modelstate.
foreach (var propertyInfo in entityFromDbProperties)
{
//If this value is not in the ModelState values, don't compare it as we don't want
//to attempt to emit model errors for fields that don't exist.
//Compare db value to the current value from the entity we posted.
if (propertyNames.Contains(propertyInfo.Name))
{
//if (propertyInfo.GetValue(databaseValues, null) != propertyInfo.GetValue(clientValues, null))
if (!Equals(propertyInfo.GetValue(databaseValues,null), propertyInfo.GetValue(clientValues,null)))
{
var currentValue = propertyInfo.GetValue(databaseValues, null);
if (currentValue == null || string.IsNullOrEmpty(currentValue.ToString()))
{
currentValue = "Empty";
}
filterContext.Controller.ViewData.ModelState.AddModelError(propertyInfo.Name, "Database value: "
+ currentValue);
}
}
//TODO: hmm.... how can we only check values applicable to the model/modelstate rather than the entity we saved?
//The problem here is we may only have a few fields used in the viewmodel, but many in the entity
//so we could have a problem here with that.
}
filterContext.ExceptionHandled = true;
filterContext.Result = new ViewResult() { ViewData = filterContext.Controller.ViewData };
}
}

Related

ABP.IO - MultiTenancy - Setting Tenant from External IDP

I am trying to configure Auth0 as an external login provider in my ABP.IO application (MVC with integrated identity server). I've got it working so that I can log in fine, but what I can't figure out is how to set the tenant in the ABP side.
What I came up with is a rule on the Auth0 side to populate the TenantId as a claim in the id token, so I can parse that in my custom SingInManager in the GetExternalLoginInfoAsync method, like so:
string tenantId = auth.Principal.FindFirstValue("https://example.com/tenantId");
I'm just having a hard time figuring out what to do with it from there. The assumption is that users will be configured to authenticate via Auth0 and the users will get created locally on first login (which, again, is working EXCEPT for the Tenant part).
Alright, here is the workaround I have in place, and it SHOULD be transferable to any external login system that you are depending on. I'm not sure if this is the correct way of doing this, so if anybody else wants to chip in with a more efficient system I am all ears.
Anyway, my workflow assumes that you have, like I did, created a mechanism for the TenantId to be sent from the external IDP. For this, I used the Organizations feature in Auth0 and added the TenantId as metadata, then I created an Action in Auth0 to attach that metadata as a claim to be used on the ABP side.
In ABP, I followed this article to override the SignInManager: https://community.abp.io/articles/how-to-customize-the-signin-manager-3e858753
As in the article, I overrode the GetExternalLoginInfoAsync method of the sign in manager and added the following lines to pull the TenantId out of the Auth0 claims and add it back in using the pre-defined AbpClaimTypes.TenantId value.
EDIT: I also had to override the ExternalLoginSignInAsync method to account for multi-tenancy (otherwise it kept trying to recreate the users and throwing duplicate email errors). I'll post the full class below with my added stuff in comments:
public class CustomSignInManager : Microsoft.AspNetCore.Identity.SignInManager<Volo.Abp.Identity.IdentityUser>
{
private const string LoginProviderKey = "LoginProvider";
private const string XsrfKey = "XsrfId";
private readonly IDataFilter _dataFilter;
public CustomSignInManager(
IDataFilter dataFilter,
Microsoft.AspNetCore.Identity.UserManager<Volo.Abp.Identity.IdentityUser> userManager,
Microsoft.AspNetCore.Http.IHttpContextAccessor contextAccessor,
Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory<Volo.Abp.Identity.IdentityUser> claimsFactory,
Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Identity.IdentityOptions> optionsAccessor,
Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Identity.SignInManager<Volo.Abp.Identity.IdentityUser>> logger,
Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes,
Microsoft.AspNetCore.Identity.IUserConfirmation<Volo.Abp.Identity.IdentityUser> confirmation)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
{
_dataFilter = dataFilter;
}
/// <summary>
/// Gets the external login information for the current login, as an asynchronous operation.
/// </summary>
/// <param name="expectedXsrf">Flag indication whether a Cross Site Request Forgery token was expected in the current request.</param>
/// <returns>The task object representing the asynchronous operation containing the <see name="ExternalLoginInfo"/>
/// for the sign-in attempt.</returns>
public override async Task<Microsoft.AspNetCore.Identity.ExternalLoginInfo> GetExternalLoginInfoAsync(string expectedXsrf = null)
{
var auth = await Context.AuthenticateAsync(IdentityConstants.ExternalScheme);
var items = auth?.Properties?.Items;
if (auth?.Principal == null || items == null || !items.ContainsKey(LoginProviderKey))
{
return null;
}
if (expectedXsrf != null)
{
if (!items.ContainsKey(XsrfKey))
{
return null;
}
var userId = items[XsrfKey] as string;
if (userId != expectedXsrf)
{
return null;
}
}
var providerKey = auth.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
var provider = items[LoginProviderKey] as string;
if (providerKey == null || provider == null)
{
return null;
}
var providerDisplayName = (await GetExternalAuthenticationSchemesAsync()).FirstOrDefault(p => p.Name == provider)?.DisplayName
?? provider;
/* Begin tenantId claim search */
string tenantId = auth.Principal.FindFirstValue("https://example.com/tenantId"); //pull the tenantId claim if it exists
if(!string.IsNullOrEmpty(tenantId))
{
auth.Principal.Identities.FirstOrDefault().AddClaim(new Claim(AbpClaimTypes.TenantId, tenantId)); //if there is a tenantId, add the AbpClaimTypes.TenantId claim back into the principal
}
/* End tenantId claim search */
var eli = new ExternalLoginInfo(auth.Principal, provider, providerKey, providerDisplayName)
{
AuthenticationTokens = auth.Properties.GetTokens(),
AuthenticationProperties = auth.Properties
};
return eli;
}
/// <summary>
/// Signs in a user via a previously registered third party login, as an asynchronous operation.
/// </summary>
/// <param name="loginProvider">The login provider to use.</param>
/// <param name="providerKey">The unique provider identifier for the user.</param>
/// <param name="isPersistent">Flag indicating whether the sign-in cookie should persist after the browser is closed.</param>
/// <param name="bypassTwoFactor">Flag indicating whether to bypass two factor authentication.</param>
/// <returns>The task object representing the asynchronous operation containing the <see name="SignInResult"/>
/// for the sign-in attempt.</returns>
public override async Task<SignInResult> ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent, bool bypassTwoFactor)
{
Volo.Abp.Identity.IdentityUser user = null; //stage the user variable as null
using (_dataFilter.Disable<IMultiTenant>()) //disable the tenantid filters so we can search all logins for the expected key
{
user = await UserManager.FindByLoginAsync(loginProvider, providerKey); //search logins for the expected key
}
if (user == null)
{
return SignInResult.Failed;
}
var error = await PreSignInCheck(user);
if (error != null)
{
return error;
}
return await SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor);
}
}
Once that was done, I tracked down where the GetExternalLoginInfoAsync was being utilized and figured out I had to override the CreateExternalUserAsync method inside of the LoginModel for the Login page. To that end, I followed the directions in this article for creating a CustomLoginModel.cs and Login.cshtml : https://community.abp.io/articles/hide-the-tenant-switch-of-the-login-page-4foaup7p
So, my Auth0LoginModel class looks like this:
public class Auth0LoginModel : LoginModel
{
public Auth0LoginModel(IAuthenticationSchemeProvider schemeProvider, IOptions<AbpAccountOptions> accountOptions, IOptions<IdentityOptions> identityOptions) : base(schemeProvider, accountOptions, identityOptions)
{
}
protected override async Task<IdentityUser> CreateExternalUserAsync(ExternalLoginInfo info)
{
await IdentityOptions.SetAsync();
var emailAddress = info.Principal.FindFirstValue(AbpClaimTypes.Email);
/* Begin TenantId claim check */
var tenantId = info.Principal.FindFirstValue(AbpClaimTypes.TenantId);
if (!string.IsNullOrEmpty(tenantId))
{
try
{
CurrentTenant.Change(Guid.Parse(tenantId));
}
catch
{
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
{
Identity = IdentitySecurityLogIdentityConsts.IdentityExternal,
Action = "Unable to parse TenantId: " + tenantId
}) ;
}
}
/* End TenantId claim check */
var user = new IdentityUser(GuidGenerator.Create(), emailAddress, emailAddress, CurrentTenant.Id);
CheckIdentityErrors(await UserManager.CreateAsync(user));
CheckIdentityErrors(await UserManager.SetEmailAsync(user, emailAddress));
CheckIdentityErrors(await UserManager.AddLoginAsync(user, info));
CheckIdentityErrors(await UserManager.AddDefaultRolesAsync(user));
return user;
}
}
The code added is between the comments, the rest of the method was pulled from the source. So I look for the AbpClaimTypes.TenantId claim being present, and if it does I attempt to use the CurrentTenant.Change method to change the tenant prior to the call to create the new IdentityUser.
Once that is done, the user gets created in the correct tenant and everything flows like expected.

Working on a Dynamics CRM Custom Workflow that updates "Modified By" field -- need help debugging

I am working on a Dynamics CRM CWA that updates the "Modified By" field based on a text field called "Prepared By". I currently have 3 errors that I need some help debugging (see below). They may be pretty easy fixes but I am fairly new to coding. Any help de-bugging would be greatly appreciated. Thanks!
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Threading.Tasks;
using System.Activities;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Workflow;
using System.Runtime.Serialization;
namespace KED365.Workflows
{
/// </summary>
public class ModifiedBy : WorkFlowActivityBase
{
private Guid contactid;
[Input("User Full Name")]
public InArgument<string> UserFullName { get; set; }
/// <summary>
/// Executes the WorkFlow.
/// </summary>
/// <param name="crmWorkflowContext">The <see cref="LocalWorkflowContext"/> which contains the
/// <param name="executionContext" > <see cref="CodeActivityContext"/>
/// </param>
/// <remarks>
/// For improved performance, Microsoft Dynamics 365 caches WorkFlow instances.
/// The WorkFlow's Execute method should be written to be stateless as the constructor
/// is not called for every invocation of the WorkFlow. Also, multiple system threads
/// could execute the WorkFlow at the same time. All per invocation state information
/// is stored in the context. This means that you should not use global variables in WorkFlows.
/// </remarks>
protected override void Execute(CodeActivityContext activityContext, IWorkflowContext workflowContext, IOrganizationService orgService, ITracingService tracingService)
{
//get entity record for which plugin was fired
Entity _target = (Entity)workflowContext.InputParameters["Target"];
//check if portaluser name is to be obtained from custom createby or from custom modifiedby
if (workflowContext.MessageName.ToUpper() == "CREATE")
{
contactid = _target.Attributes.Contains("new_createdby") ? _target.GetAttributeValue<EntityReference>("new_createdby").Id : Guid.Empty;
}
else
{
contactid = _target.Attributes.Contains("new_modifiedby") ? _target.GetAttributeValue<EntityReference>("new_modifiedby").Id : Guid.Empty;
}
//retrieve contact fullname from contactid
var _contact = activityContext.CreateQuery("contact").Where(c => c.GetAttributeValue<Guid>("contactid").Equals(contactid)).FirstOrDefault();
if (_contact != null)
{
if (_contact.Attributes.Contains("fullname"))
{
fullname = _contact.GetAttributeValue<string>("fullname");
}
//retrieve Systemuser that has same name as that of new_portalcreatedby/ //new_portalmodifiedby
Entity _user = context.CreateQuery("systemuser").Where(e => e.GetAttributeValue<string>("fullname").Equals(fullname)).FirstOrDefault();
if (_user != null)
{
//check if we need to update createdby or modifiedby
if (workflowContext.MessageName.ToUpper() == "CREATE")
{
_target["createdby"] = _user.ToEntityReference();
}
else
{
_target["modifiedby"] = _user.ToEntityReference();
}
//assign new target to plugin executioncontext
workflowContext.InputParameters["Target"] = _target;
}
}
}
}
}
Error 1 :
Severity Code Description Project File Line Suppression State
Error CS1061 'CodeActivityContext' does not contain a definition for 'CreateQuery' and no extension method 'CreateQuery' accepting a first argument of type 'CodeActivityContext' could be found (are you missing a using directive or an assembly reference?) Workflows C:\Users\tgiard\Downloads\GetUserByName-master\GetUserByName-master\Workflows\ModifiedBy.cs 68 Active
Error 2 :
Severity Code Description Project File Line Suppression State
Error CS0103 The name 'fullname' does not exist in the current context Workflows C:\Users\tgiard\Downloads\GetUserByName-master\GetUserByName-master\Workflows\ModifiedBy.cs 75 Active
Error 3 :
Severity Code Description Project File Line Suppression State
Error CS0103 The name 'context' does not exist in the current context Workflows C:\Users\tgiard\Downloads\GetUserByName-master\GetUserByName-master\Workflows\ModifiedBy.cs 79 Active
Here is some feedback on your issues:
Error 1 - 'CodeActivityContext' does not contain a definition for 'CreateQuery'
This issue is related to the lines:
var _contact = activityContext.CreateQuery("contact").Where(c => c.GetAttributeValue<Guid>("contactid").Equals(contactid)).FirstOrDefault();
and
Entity _user = context.CreateQuery("systemuser").Where(e => e.GetAttributeValue<string>("fullname").Equals(fullname)).FirstOrDefault();
I don't know what that method is but you have better options; for the contact you already have the guid, so you can simply use a Retrieve():
var _contact = orgService.Retrieve("contact", contactid, new ColumnSet("fullname"));
And for the system user write a QueryExpression filtering by fullname:
var query = new QueryExpression("systemuser"):
query.Criteria.AddCondition("fullname", ConditionOperator.Equal, fullname);
var _user = orgService.RetrieveMultiple(query).Entities.FirstOrDefault();
Error 2: The name 'fullname' does not exist in the current contex
This is basic C#, you must instantiate your variable before you use it:
string fullname;
Error 3: The name 'context' does not exist in the current context
Ironic and true. This should be activityContext, but we have already fixed this issue in the change we made for Error 1.
Entity _user = context.CreateQuery("systemuser").Where(e => e.GetAttributeValue<string>("fullname").Equals(fullname)).FirstOrDefault();
As Zach Mast correctly pointed out, using a Pre-Operation is recommended. It also seems an odd case where you retrieve a contacts name and match it with a user. Instead, You could change the type of the field to a user reference, add a user field to the contact you retrieve or add a code to match the Contact to the User. This way, you won't have issues with users with the same name or typo's.
Please find below your workflow activity converted to a Pre-Operation plugin.
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Linq;
namespace KED365.Plugins
{
public class CreateUpdateContact : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
var service = factory.CreateOrganizationService(context.UserId);
tracingService.Trace("Start plugin");
tracingService.Trace("Validate Target");
if (!context.InputParameters.Contains("Target") || !(context.InputParameters["Target"] is Entity))
return;
tracingService.Trace("Retrieve Target");
var target = (Entity)context.InputParameters["Target"];
String message = context.MessageName.ToLower();
SetCreatedByAndModifiedBy(tracingService, service, target, message);
}
private void SetCreatedByAndModifiedBy(ITracingService tracingService, IOrganizationService service, Entity target, string message)
{
tracingService.Trace("Start SetPriceList");
tracingService.Trace("Validate Message is Create or Update");
if (!message.Equals("create", StringComparison.OrdinalIgnoreCase) && !message.Equals("update", StringComparison.OrdinalIgnoreCase))
return;
tracingService.Trace("Retrieve Attributes");
var createdByReference = target.GetAttributeValue<EntityReference>("new_createdby");
var modifiedByReference = target.GetAttributeValue<EntityReference>("new_modifiedby");
tracingService.Trace("Retrieve And Set User for Created By");
RetrieveAndSetUser(tracingService, service, target, createdByReference, "createdby");
tracingService.Trace("Retrieve And Set User for Modified By");
RetrieveAndSetUser(tracingService, service, target, modifiedByReference, "modifiedby");
}
private void RetrieveAndSetUser(ITracingService tracingService, IOrganizationService service, Entity target, EntityReference reference, string targetAttribute)
{
tracingService.Trace("Validating Reference");
if (reference == null)
return;
tracingService.Trace("Retrieving and Validating User");
var user = RetrieveUserByName(service, reference.Name, new ColumnSet(false));
if (user == null)
return;
tracingService.Trace("Setting Target Attribute");
target[targetAttribute] = user.ToEntityReference();
}
private Entity RetrieveUserByName(IOrganizationService service, string name, ColumnSet columns)
{
var query = new QueryExpression
{
EntityName = "systemuser",
ColumnSet = columns,
Criteria = new FilterExpression
{
FilterOperator = LogicalOperator.And,
Conditions =
{
new ConditionExpression
{
AttributeName = "fullname",
Operator = ConditionOperator.Equal,
Values = { name }
}
}
}
};
var retrieveResponse = service.RetrieveMultiple(query);
if (retrieveResponse.Entities.Count == 1)
{
return retrieveResponse.Entities.FirstOrDefault();
}
else
{
// Alternatively you can thrown an error as you have unexpectedly multiple matches
return null;
}
}
}
}

Extending Umbraco ProfileModel with custom member model

I have the following code while updating the member account detail information:
/// <summary>
/// Verifies and edits the member fields.
/// </summary>
/// <param name="model"></param>
/// <returns>MemberDetailsFormViewModel containing all the editable information.</returns>
[Authorize]
public ActionResult HandleUpdateMemberDetails(MemberDetailsFormViewModel model)
{
if (ModelState.IsValid == false)
{
return CurrentUmbracoPage();
}
var memberService = Services.MemberService;
var member = memberService.GetById(Members.GetCurrentMemberId());
member.Properties[Constant.PropertyAlias.Authentication.FirstName].Value = model.FirstName;
member.Properties[Constant.PropertyAlias.Authentication.LastName].Value = model.LastName;
member.Properties[Constant.PropertyAlias.Authentication.AddressLine1].Value = model.AddressLine1;
member.Properties[Constant.PropertyAlias.Authentication.AddressLine2].Value = model.AddressLine2;
member.Properties[Constant.PropertyAlias.Authentication.TownCity].Value = model.TownCity;
member.Properties[Constant.PropertyAlias.Authentication.PostCode].Value = model.PostCode;
member.Properties[Constant.PropertyAlias.Authentication.County].Value = model.County;
member.Properties[Constant.PropertyAlias.Authentication.Country].Value = model.Country;
member.Properties[Constant.PropertyAlias.Authentication.PhoneNumber].Value = model.PhoneNumber;
memberService.Save(member);
if (Members.IsLoggedIn())
{
ViewBag.DetailSuccessfullyChanged = 1;
return CurrentUmbracoPage();
}
return View("/");
}
Although this solution works, I would like to do more elegant updating of the member.Properties["propertyName"].Value through extended ProfileModel with the following method of MembershipHelper: UpdateMemberProfile(ProfileModel model).
Useful links:
MembershipHelper documentation link.
Similar issue on OUR Umbraco
Has anyone managed to do something like this?
EDIT:
I see that Warren did something similiar on his GitHub.

ASP.NET MVC datetime culture issue when passing value back to controller

How can i tell my controller/model what kind of culture it should expect for parsing a datetime?
I was using some of this post to implement jquery datepicker into my mvc application.
When i submit the date it gets "lost in translation" i'm not using the US formatting for the date, so when it gets sent to my controller it simply becomes null.
I have a form where the user chooses a date:
#using (Html.BeginForm("List", "Meter", FormMethod.Get))
{
#Html.LabelFor(m => m.StartDate, "From:")
<div>#Html.EditorFor(m => m.StartDate)</div>
#Html.LabelFor(m => m.EndDate, "To:")
<div>#Html.EditorFor(m => m.EndDate)</div>
}
I've made an edit template for this, to implement the jquery datepicker:
#model DateTime
#Html.TextBox("", Model.ToString("dd-MM-yyyy"), new { #class = "date" })
I then create the datepicker widgets like this.
$(document).ready(function () {
$('.date').datepicker({ dateFormat: "dd-mm-yy" });
});
All this works fine.
Here is where the problems start, this is my controller:
[HttpGet]
public ActionResult List(DateTime? startDate = null, DateTime? endDate = null)
{
//This is where startDate and endDate becomes null if the dates dont have the expected formatting.
}
This is why i would like to somehow tell my controller what culture it should expect?
Is my model wrong? can i somehow tell it which culture to use, like with the data annotation attributes?
public class MeterViewModel {
[Required]
public DateTime StartDate { get; set; }
[Required]
public DateTime EndDate { get; set; }
}
Edit: this link explains my issue and a very good solution to it aswell. Thanks to gdoron
you can change the default model binder to use the user culture using IModelBinder
public class DateTimeBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var date = value.ConvertTo(typeof(DateTime), CultureInfo.CurrentCulture);
return date;
}
}
And in the Global.Asax write:
ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
ModelBinders.Binders.Add(typeof(DateTime?), new DateTimeBinder());
Read more at this excellent blog that describe why Mvc framework team implemented a default Culture to all users.
You can create a Binder extension to handle the date in the culture format.
This is a sample I wrote to handle the same problem with Decimal type, hope you get the idea
public class DecimalModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
ModelState modelState = new ModelState { Value = valueResult };
object actualValue = null;
try
{
actualValue = Convert.ToDecimal(valueResult.AttemptedValue, CultureInfo.CurrentCulture);
}
catch (FormatException e)
{
modelState.Errors.Add(e);
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return actualValue;
}
}
Update
To use it simply declare the binder in Global.asax like this
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
//HERE you tell the framework how to handle decimal values
ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
DependencyResolver.SetResolver(new ETAutofacDependencyResolver());
}
Then when the modelbinder has to do some work, it will know automatically what to do.
For example, this is an action with a model containing some properties of type decimal. I simply do nothing
[HttpPost]
public ActionResult Edit(int id, MyViewModel viewModel)
{
if (ModelState.IsValid)
{
try
{
var model = new MyDomainModelEntity();
model.DecimalValue = viewModel.DecimalValue;
repository.Save(model);
return RedirectToAction("Index");
}
catch (RulesException ex)
{
ex.CopyTo(ModelState);
}
catch
{
ModelState.AddModelError("", "My generic error message");
}
}
return View(model);
}
This issue arises because you are using the GET method on your Form. The QueryString Value Provider in MVC always uses Invariant/US date format. See: MVC DateTime binding with incorrect date format
There are three solutions:
Change your method to POST.
As someone else says, change the date format to ISO 8601 "yyyy-mm-dd" before submission.
Use a custom binder to always treat Query String dates as GB. If you do this you have to make sure that all dates are in that form:
public class UKDateTimeModelBinder : IModelBinder
{
private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
/// <summary>
/// Fixes date parsing issue when using GET method. Modified from the answer given here:
/// https://stackoverflow.com/questions/528545/mvc-datetime-binding-with-incorrect-date-format
/// </summary>
/// <param name="controllerContext">The controller context.</param>
/// <param name="bindingContext">The binding context.</param>
/// <returns>
/// The converted bound value or null if the raw value is null or empty or cannot be parsed.
/// </returns>
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var vpr = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (vpr == null)
{
return null;
}
var date = vpr.AttemptedValue;
if (String.IsNullOrEmpty(date))
{
return null;
}
logger.DebugFormat("Parsing bound date '{0}' as UK format.", date);
// Set the ModelState to the first attempted value before we have converted the date. This is to ensure that the ModelState has
// a value. When we have converted it, we will override it with a full universal date.
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, bindingContext.ValueProvider.GetValue(bindingContext.ModelName));
try
{
var realDate = DateTime.Parse(date, System.Globalization.CultureInfo.GetCultureInfoByIetfLanguageTag("en-GB"));
// Now set the ModelState value to a full value so that it can always be parsed using InvarianCulture, which is the
// default for QueryStringValueProvider.
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, new ValueProviderResult(date, realDate.ToString("yyyy-MM-dd hh:mm:ss"), System.Globalization.CultureInfo.GetCultureInfoByIetfLanguageTag("en-GB")));
return realDate;
}
catch (Exception)
{
logger.ErrorFormat("Error parsing bound date '{0}' as UK format.", date);
bindingContext.ModelState.AddModelError(bindingContext.ModelName, String.Format("\"{0}\" is invalid.", bindingContext.ModelName));
return null;
}
}
}
When submitting a date you should always try and submit it in the format "yyyy-MM-dd". This will allow for it to become culture independent.
I normally have a hidden field which maintains the date in this format. This is relatively simple using jQuery UI's datepicker.
Why not simply inspect the culture of the data and convert it as such? This simple approach allowed me to use strongly typed dates in models, show action links and edit fields in the desired locale and not have to fuss at all binding it back into a strongly typed DateTime:
public class DateTimeBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
return value.ConvertTo(typeof(DateTime), value.Culture);
}
}
that did the trick for me
<system.web>
<globalization enableClientBasedCulture="true" uiCulture="Auto" culture="Auto" />
</system.web>
I have a updated solution for MVC5 based on the Post of #gdoron. I will share it in case anyone else is looking for this. The class inherits from DefaultModelBinder and has exception handling for invalid dates. It also can handle null values:
public class DateTimeModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object result = null;
var modelName = bindingContext.ModelName;
var attemptedValue = bindingContext.ValueProvider.GetValue(modelName)?.AttemptedValue;
// in datetime? binding attemptedValue can be Null
if (attemptedValue != null && !string.IsNullOrWhiteSpace(attemptedValue))
{
try
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
result = DateTime.Parse(value.AttemptedValue, CultureInfo.CurrentCulture);
}
catch (FormatException e)
{
bindingContext.ModelState.AddModelError(modelName, e);
}
}
return result;
}
}
And just like the mentioned sample in the Global.Asax write
ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
ModelBinders.Binders.Add(typeof(DateTime?), new DateTimeBinder());

How to convert IEnumerable to Subsonic collection?

Embarrassing question really -- I have Subsonic collection, then I filter out some data using Where.
MyColl.Where(it => it.foo()==true)
now I would like to pass those data still as Subsonic collection. How to do it (the most properly way)? I checked constructor for Subsonic collection, ToX() methods, and googled.
edit: Subsonic 2.x series.
Thank you in advance, and sorry for naive question.
In SubSonic 2.x, the data isn't actually filtered out with Where() until the query is re-executed against the database. I'm assuming that's the same in 3.0. In 2.x there was a .Filter() method that would do what you are looking for.
The .Filter() method gets added to the generated classes. Here's what mine looks like (it's customized from the default):
/// <summary>
/// Filters an existing collection based on the set criteria. This is an in-memory filter.
/// All existing wheres are retained.
/// </summary>
/// <returns>TblSomethingOrOtherCollection</returns>
public TblSomethingOrOtherCollection Filter(SubSonic.Where w)
{
return Filter(w, false);
}
/// <summary>
/// Filters an existing collection based on the set criteria. This is an in-memory filter.
/// Existing wheres can be cleared if not needed.
/// </summary>
/// <returns>TblSomethingOrOtherCollection</returns>
public TblSomethingOrOtherCollection Filter(SubSonic.Where w, bool clearWheres)
{
if (clearWheres)
{
this.wheres.Clear();
}
this.wheres.Add(w);
return Filter();
}
/// <summary>
/// Filters an existing collection based on the set criteria. This is an in-memory filter.
/// Thanks to developingchris for this!
/// </summary>
/// <returns>TblSomethingOrOtherCollection</returns>
public TblSomethingOrOtherCollection Filter()
{
for (int i = this.Count - 1; i > -1; i--)
{
TblSomethingOrOther o = this[i];
foreach (SubSonic.Where w in this.wheres)
{
bool remove = false;
System.Reflection.PropertyInfo pi = o.GetType().GetProperty(w.ColumnName);
if (pi != null && pi.CanRead)
{
object val = pi.GetValue(o, null);
if (w.ParameterValue is Array)
{
Array paramValues = (Array)w.ParameterValue;
foreach (object arrayVal in paramValues)
{
remove = !Utility.IsMatch(w.Comparison, val, arrayVal);
if (remove)
break;
}
}
else
{
remove = !Utility.IsMatch(w.Comparison, val, w.ParameterValue);
}
}
if (remove)
{
this.Remove(o);
break;
}
}
}
return this;
}
}
For some reason i could never get the inline method of filter to work, but its easy to use like this:
SubSonic.Where w = new Where();
w.ColumnName = Product.CatIDColumn.PropertyName;
w.Comparison = Comparison.Equals;
w.ParameterValue = "1";
ProductCollection objFilteredCol = objProdCollection.Where(w).Filter();

Resources