When creating a user like so:
using var trans = Db.BeginTransaction();
AuthRepository.CreateUserAuth(newUser, request.Password);
AuthRepository.AssignRoles(created, new List<string> { request.role });
//.. do other stuff
throw new Exception("other code may throw this");
trans.Commit();
The Auth repo has it's own connection so it's not part of the transaction. This means, if my code bails out, I end up with an unwanted user.
Is there any way to use a transaction with AuthRepository or is only way to manually write to user and role tables? I couldn't find an example of creating the password hash in docs when manually saving, is there any example?
You can't use the existing Auth Repository APIs within a transaction because each uses its own DB connection.
If you want you can take the implementation of those OrmLite Auth Respository APIs in OrmLiteAuthRepository.cs and move them into your method so they're all using the same DB Connection + Transaction.
Also note that to use Transactions in OrmLite you should use OpenTransaction() instead, e.g:
using (var dbTrans = db.OpenTransaction())
{
}
Adding class I used here in case it helps anyone as needed a little refactoring:
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using OutReachPete.ServiceModel.User;
using ServiceStack;
using ServiceStack.Auth;
using ServiceStack.OrmLite;
namespace OutReachPete.ServiceInterface.User
{
public class UserHelper
{
public static bool ForceCaseInsensitiveUserNameSearch { get; set; } = true;
public static bool UseDistinctRoleTables { get; set; } = true;
public static UserAuthCustom CreateUserAuth(UserAuthCustom newUser, string password, IDbConnection db)
{
newUser.ValidateNewUser(password);
AssertNoExistingUser(db, newUser);
newUser.PopulatePasswordHashes(password);
newUser.CreatedDate = DateTime.UtcNow;
newUser.ModifiedDate = newUser.CreatedDate;
db.Save(newUser);
newUser = db.SingleById<UserAuthCustom>(newUser.Id);
return newUser;
}
public static void AssertNoExistingUser(IDbConnection db, IUserAuth newUser,
IUserAuth exceptForExistingUser = null)
{
if (newUser.UserName != null)
{
var existingUser = GetUserAuthByUserName(db, newUser.UserName);
if (existingUser != null
&& (exceptForExistingUser == null || existingUser.Id != exceptForExistingUser.Id))
throw new ArgumentException(string.Format(ErrorMessages.UserAlreadyExistsTemplate1,
newUser.UserName.SafeInput()));
}
if (newUser.Email != null)
{
var existingUser = GetUserAuthByUserName(db, newUser.Email);
if (existingUser != null
&& (exceptForExistingUser == null || existingUser.Id != exceptForExistingUser.Id))
throw new ArgumentException(string.Format(ErrorMessages.EmailAlreadyExistsTemplate1,
newUser.Email.SafeInput()));
}
}
public static UserAuthCustom GetUserAuthByUserName(IDbConnection db, string userNameOrEmail)
{
var isEmail = userNameOrEmail.Contains("#");
var lowerUserName = userNameOrEmail.ToLower();
UserAuthCustom userAuth = null;
// Usernames/Emails are saved in Lower Case so we can do an exact search using lowerUserName
if (HostContext.GetPlugin<AuthFeature>()?.SaveUserNamesInLowerCase == true)
{
return isEmail
? db.Select<UserAuthCustom>(q => q.Email == lowerUserName).FirstOrDefault()
: db.Select<UserAuthCustom>(q => q.UserName == lowerUserName).FirstOrDefault();
}
// Try an exact search using index first
userAuth = isEmail
? db.Select<UserAuthCustom>(q => q.Email == userNameOrEmail).FirstOrDefault()
: db.Select<UserAuthCustom>(q => q.UserName == userNameOrEmail).FirstOrDefault();
if (userAuth != null)
return userAuth;
// Fallback to a non-index search if no exact match is found
if (ForceCaseInsensitiveUserNameSearch)
{
userAuth = isEmail
? db.Select<UserAuthCustom>(q => q.Email.ToLower() == lowerUserName).FirstOrDefault()
: db.Select<UserAuthCustom>(q => q.UserName.ToLower() == lowerUserName).FirstOrDefault();
}
return userAuth;
}
public static void AssignRoles(IAuthRepository userAuthRepo, IUserAuth userAuth, IDbConnection db,
ICollection<string> roles = null, ICollection<string> permissions = null)
{
if (userAuthRepo is IManageRoles managesRoles)
{
AssignRoles(userAuth.Id.ToString(), db, roles, permissions);
}
else
{
AssignRolesInternal(userAuth, roles, permissions);
SaveUserAuth(userAuth, db);
}
}
public static IUserAuth GetUserAuth(string userAuthId, IDbConnection db)
{
if (string.IsNullOrEmpty(userAuthId))
throw new ArgumentNullException(nameof(userAuthId));
return db.SingleById<UserAuthCustom>(int.Parse(userAuthId));
}
public static void AssignRoles(string userAuthId, IDbConnection db, ICollection<string> roles = null, ICollection<string> permissions = null)
{
var userAuth = GetUserAuth(userAuthId, db);
if (!UseDistinctRoleTables)
{
if (!roles.IsEmpty())
{
foreach (var missingRole in roles.Where(x => userAuth.Roles == null || !userAuth.Roles.Contains(x)))
{
if (userAuth.Roles == null)
userAuth.Roles = new List<string>();
userAuth.Roles.Add(missingRole);
}
}
if (!permissions.IsEmpty())
{
foreach (var missingPermission in permissions.Where(x => userAuth.Permissions == null || !userAuth.Permissions.Contains(x)))
{
if (userAuth.Permissions == null)
userAuth.Permissions = new List<string>();
userAuth.Permissions.Add(missingPermission);
}
}
SaveUserAuth(userAuth, db);
}
else
{
var now = DateTime.UtcNow;
var userRoles = db.Select<UserAuthRole>(q => q.UserAuthId == userAuth.Id);
if (!roles.IsEmpty())
{
var roleSet = userRoles.Where(x => x.Role != null).Select(x => x.Role).ToHashSet();
foreach (var role in roles)
{
if (!roleSet.Contains(role))
{
db.Insert(new UserAuthRole
{
UserAuthId = userAuth.Id,
Role = role,
CreatedDate = now,
ModifiedDate = now,
});
}
}
}
if (!permissions.IsEmpty())
{
var permissionSet = userRoles.Where(x => x.Permission != null).Select(x => x.Permission).ToHashSet();
foreach (var permission in permissions)
{
if (!permissionSet.Contains(permission))
{
db.Insert(new UserAuthRole
{
UserAuthId = userAuth.Id,
Permission = permission,
CreatedDate = now,
ModifiedDate = now,
});
}
}
}
}
}
private static void AssignRolesInternal(IUserAuth userAuth, ICollection<string> roles, ICollection<string> permissions)
{
if (!roles.IsEmpty())
{
foreach (var missingRole in roles.Where(x => userAuth.Roles == null || !userAuth.Roles.Contains(x)))
{
if (userAuth.Roles == null)
userAuth.Roles = new List<string>();
userAuth.Roles.Add(missingRole);
}
}
if (!permissions.IsEmpty())
{
foreach (var missingPermission in permissions.Where(x =>
userAuth.Permissions == null || !userAuth.Permissions.Contains(x)))
{
if (userAuth.Permissions == null)
userAuth.Permissions = new List<string>();
userAuth.Permissions.Add(missingPermission);
}
}
}
public static void SaveUserAuth(IUserAuth userAuth, IDbConnection db)
{
if (userAuth == null)
throw new ArgumentNullException(nameof(userAuth));
userAuth.ModifiedDate = DateTime.UtcNow;
if (userAuth.CreatedDate == default(DateTime))
userAuth.CreatedDate = userAuth.ModifiedDate;
db.Save((UserAuthCustom)userAuth);
}
}
}
Then just call it like:
var created = UserHelper.CreateUserAuth(newUser, request.Password, Db);
UserHelper.AssignRoles(AuthRepository, created, Db, new List<string> { request.CreateUserType.ToString() });
It will only use the connection passed. Just change UserAuthCustom to whatever you user class is.
Related
I have the following issue using ASP.NET Core 2.2 and Automapper 9.0.0:
I have a Entity that I map to a dto, this works fine. Inside that entity are a few entities as well. These cannot be flattened as they are also needed by our client. There is however one property within those nested entities that need additional mapping.
within the Profile class of the overlapping Entity i do this:
public class OperationalToPalletResultProfile : Profile
{
public OperationalToPalletResultProfile()
{
string lang = null;
CreateMap<TblDatOperationalHUTrace, PalletResult>()
.ForMember(dest => dest.TransCode, act => act.MapFrom((src, _, transactionCode, ctx) =>
ctx.Mapper.Map<TblLstCode, TransactionCodeResult>(src.TransCode)));
}
}
This effectively maps the TblLstCode to TransactionCodeResult correctly. However the "DisplayDesc" property of the TransactionCodeResult remains null...
Note: the "lang" property gets set with the "ProjectTo" method:
var values = await query
.Take(5000)
.ProjectTo<PalletResult>(mapper.ConfigurationProvider, new { lang = language })
.ToListAsync()
.ConfigureAwait(false);
I have tried:
1- Using "AfterMap" in Profile class (result: DisplayDesc = null)
CreateMap<TblDatOperationalHUTrace, PalletResult>()
.ForMember(dest => dest.TransCode, act => act.MapFrom((src, _, transactionCode, ctx) =>
ctx.Mapper.Map<TblLstCode, TransactionCodeResult>(src.TransCode, opt =>
opt.AfterMap((source, destination) =>
destination.DisplayDesc = $"[{destination.ShortName}] {destination.DescToLanguageDesc(destination, lang)}"))))
Note: DescToLanguageDesc is a function that finds a property in the entity with reflection (this works and is not part of the problem)
2- Creating an IMappingAction to use in the Profile of the SubEntity (result: DisplayDesc = null)
public class TransactionCodeTranslation : IMappingAction<TblLstCode, TransactionCodeResult>
{
private readonly IHttpContextAccessor httpContextAccessor;
public TransactionCodeTranslation(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public void Process(TblLstCode source, TransactionCodeResult destination, ResolutionContext context)
{
//Get language from httpContext - this works correctly
destination.DisplayDesc = $"[{source.ShortName}] {source.DescToLanguageDesc(source, language)}";
}
}
public class TransactionCodeProfile : Profile
{
public TransactionCodeProfile()
{
string language = string.Empty;
CreateMap<TblLstCode, TransactionCodeResult>()
.AfterMap<TransactionCodeTranslation>();
}
}
This doesn't work. HOWEVER if I use option 2 directly:
var tc = await codeRepo.TableNoTracking.FirstOrDefaultAsync(x => x.ShortName == "[something]").ConfigureAwait(false);
var transactionCode = mapper.Map<TblLstCode, TransactionCodeResult>(tc);
Then it works correctly! But that would mean i would have to loop my result and map every object in the result again...
Is there a way to do it like option 1?
Thank you!
Edit 1:
Per Lucian Bargaoanu's request i added a BuildExecutionPlan:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<TblDatOperationalHUTrace, PalletResult>()
.ForMember(dest => dest.TransCode, act => act.MapFrom((src, _, transactionCode, ctx) =>
ctx.Mapper.Map<TblLstCode, TransactionCodeResult>(src.TransCode, opt =>
opt.AfterMap((source, destination) =>
destination.DisplayDesc = $"[{destination.ShortName}] {destination.DescToLanguageDesc(destination, language)}"))));
cfg.CreateMap<TblLstCode, TransactionCodeResult>()
.AfterMap<TransactionCodeTranslation>();
});
var expression = config.BuildExecutionPlan(typeof(TblDatOperationalHUTrace), typeof(PalletResult));
var expression2 = config.BuildExecutionPlan(typeof(TblLstCode), typeof(TransactionCodeResult));
Result of expression:
(src, dest, ctxt) =>
{
PalletResult typeMapDestination;
return (src == null)
? null
: {
typeMapDestination = dest ?? new PalletResult();
try
{
var resolvedValue = mappingFunction.Invoke(
src,
typeMapDestination,
typeMapDestination.TransCode,
ctxt);
var propertyValue = (resolvedValue == null) ? null : resolvedValue;
typeMapDestination.TransCode = propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return null;
}
return typeMapDestination;
};
}
Result Expression2:
(src, dest, ctxt) =>
{
TransactionCodeResult typeMapDestination;
return (src == null)
? null
: {
typeMapDestination = dest ?? new TransactionCodeResult();
try
{
var resolvedValue = ((src == null) || false) ? default(int) : src.Id;
typeMapDestination.Id = resolvedValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return default(int);
}
try
{
var resolvedValue = ((src == null) || false) ? null : src.ShortName;
var propertyValue = (resolvedValue == null) ? null : resolvedValue;
typeMapDestination.ShortName = propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return null;
}
try
{
var resolvedValue = ((src == null) || false) ? default(int) : src.CodeTypeId;
typeMapDestination.CodeTypeId = resolvedValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return default(int);
}
try
{
var resolvedValue = ((src == null) || false) ? null : src.DescLC;
var propertyValue = (resolvedValue == null) ? null : resolvedValue;
typeMapDestination.DescLC = propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return null;
}
try
{
var resolvedValue = ((src == null) || false) ? null : src.DescEN;
var propertyValue = (resolvedValue == null) ? null : resolvedValue;
typeMapDestination.DescEN = propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return null;
}
try
{
var resolvedValue = ((src == null) || false) ? null : src.DescFR;
var propertyValue = (resolvedValue == null) ? null : resolvedValue;
typeMapDestination.DescFR = propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return null;
}
try
{
var resolvedValue = ((src == null) || false) ? null : src.DescGE;
var propertyValue = (resolvedValue == null) ? null : resolvedValue;
typeMapDestination.DescGE = propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return null;
}
try
{
var resolvedValue = ((src == null) || false) ? null : src.DescNL;
var propertyValue = (resolvedValue == null) ? null : resolvedValue;
typeMapDestination.DescNL = propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return null;
}
afterFunction.Invoke(src, typeMapDestination, ctxt);
return typeMapDestination;
};
}
EDIT 2:
Ok, I actually found the solution. I was overcomplicating things for no reason...
I changed OperationalToPalletResultProfile to:
public class OperationalToPalletResultProfile : Profile
{
public OperationalToPalletResultProfile()
{
CreateMap<TblDatOperationalHUTrace, PalletResult>();
}
}
The TransactionCodeProfile remained the same:
public class TransactionCodeProfile : Profile
{
public TransactionCodeProfile()
{
CreateMap<TblLstCode, TransactionCodeResult>()
.AfterMap<TransactionCodeTranslation>();
}
}
And then where i use my mapping i changed this:
//results as TblDatOperationalHUTrace
var queryresult = await query
.Take(5000)
//.ProjectTo<PalletResult>(mapper.ConfigurationProvider) Don't do the mapping here anymore
.ToListAsync()
.ConfigureAwait(false);
//Map results to PalletResult
var values = mapper.Map<List<TblDatOperationalHUTrace>, List<PalletResult>>(queryresult); //Do the mapping here
Ok i actually found the solution myself:
I changed OperationalToPalletResultProfile to:
public class OperationalToPalletResultProfile : Profile
{
public OperationalToPalletResultProfile()
{
CreateMap<TblDatOperationalHUTrace, PalletResult>();
}
}
The TransactionCodeProfile remained the same:
public class TransactionCodeProfile : Profile
{
public TransactionCodeProfile()
{
CreateMap<TblLstCode, TransactionCodeResult>()
.AfterMap<TransactionCodeTranslation>();
}
}
And then where i use my mapping i changed this:
//results as TblDatOperationalHUTrace
var queryresult = await query
.Take(5000)
//.ProjectTo<PalletResult>(mapper.ConfigurationProvider) Don't do the mapping here anymore
.ToListAsync()
.ConfigureAwait(false);
//Map results to PalletResult
var values = mapper.Map<List<TblDatOperationalHUTrace>, List<PalletResult>>(queryresult); //Do the mapping here
And then it works like it's supposed to.
I need to be able to send an email to the original requester when a PO is created from a Requisition in Acumatica 6.1.
Per Acumatica, the Notification screen cannot handle this functionality, so I have this code to extend the POOrder Entry graph, which sends an email to the Customer's contact email from the Requisition when a PO is created (along with the RQRequisitionEntryExt trigger):
public class POOrderEntryExt : PXGraphExtension<POOrderEntry>
{
private bool sendEmailNotification = false;
public bool SendEmailNotification
{
get
{
return sendEmailNotification;
}
set
{
sendEmailNotification = value;
}
}
[PXOverride]
public void Persist(Action del)
{
using (var ts = new PXTransactionScope())
{
if (del != null)
{
del();
}
if (SendEmailNotification)
{
bool sent = false;
string sError = "Failed to send E-mail.";
try
{
Notification rowNotification = PXSelect<Notification,
Where<Notification.name, Equal<Required<Notification.name>>>>
.Select(Base, "PurchaseOrderNotification");
if (rowNotification == null)
throw new PXException("Notification Template was not found.");
var order = Base.Document.Current;
var requisition = (RQRequisition)PXSelect<RQRequisition,
Where<RQRequisition.reqNbr, Equal<Current<POOrder.rQReqNbr>>>>
.SelectSingleBound(Base, new object[] { order });
if (requisition.CustomerID != null)
{
var customer = (BAccountR)PXSelectorAttribute.Select<RQRequisition.customerID>(
Base.Caches[typeof(RQRequisition)], requisition);
if (customer != null)
{
var defCustContact = (Contact)PXSelectorAttribute.Select<BAccountR.defContactID>(
Base.Caches[typeof(BAccountR)], customer);
if (String.IsNullOrEmpty(defCustContact.EMail))
throw new PXException("E-mail is not specified for Customer Contact.");
var sender = TemplateNotificationGenerator.Create(order,
rowNotification.NotificationID.Value);
sender.RefNoteID = order.NoteID;
sender.MailAccountId = rowNotification.NFrom.HasValue ?
rowNotification.NFrom.Value :
PX.Data.EP.MailAccountManager.DefaultMailAccountID;
sender.To = defCustContact.EMail;
sent |= sender.Send().Any();
}
}
}
catch (Exception Err)
{
sent = false;
sError = Err.Message;
}
if (!sent)
throw new PXException(sError);
}
ts.Complete();
}
}
}
And this to modify RQRequisitionEntry:
public class RQRequisitionEntryExt : PXGraphExtension<RQRequisitionEntry>
{
public PXAction<RQRequisition> createPOOrder;
[PXButton(ImageKey = PX.Web.UI.Sprite.Main.DataEntry)]
[PXUIField(DisplayName = Messages.CreateOrders)]
public IEnumerable CreatePOOrder(PXAdapter adapter)
{
PXGraph.InstanceCreated.AddHandler<POOrderEntry>((graph) =>
{
graph.GetExtension<POOrderEntryExt>().SendEmailNotification = true;
});
return Base.createPOOrder.Press(adapter);
}
}
In order to send an email to the Requester's (Employee) contact email from the Request, I modified the POOrderEntryExt to pull the information from the Request object and the Employee's Contact email (I left the RQRequisitionEntryExt the same and in place):
public class POOrderEntryExt : PXGraphExtension<POOrderEntry>
{
private bool sendEmailNotification = false;
public bool SendEmailNotification
{
get
{
return sendEmailNotification;
}
set
{
sendEmailNotification = value;
}
}
[PXOverride]
public void Persist(Action del)
{
using (var ts = new PXTransactionScope())
{
if (del != null)
{
del();
}
if (SendEmailNotification)
{
bool sent = false;
string sError = "Failed to send E-mail.";
try
{
Notification rowNotification = PXSelect<Notification,
Where<Notification.name, Equal<Required<Notification.name>>>>
.Select(Base, "PurchaseOrderNotification");
if (rowNotification == null)
throw new PXException("Notification Template was not found.");
var order = Base.Document.Current;
var requisition = (RQRequisition)PXSelect<RQRequisition,
Where<RQRequisition.reqNbr, Equal<Current<POOrder.rQReqNbr>>>>
.SelectSingleBound(Base, new object[] { order });
var request = (RQRequest)PXSelectJoin<RQRequest,
InnerJoin<RQRequisitionContent,
On<RQRequisitionContent.orderNbr, Equal<RQRequest.orderNbr>>>,
Where<RQRequisitionContent.reqNbr, Equal<POOrder.rQReqNbr>>>
.SelectSingleBound(Base, new object[] { order });
if (request.EmployeeID != null)
{
var employee = (BAccountR)PXSelectorAttribute.Select<RQRequest.employeeID>(
Base.Caches[typeof(RQRequest)], request);
if (employee != null)
{
var defEmpContact = (Contact)PXSelectorAttribute.Select<BAccountR.defContactID>(
Base.Caches[typeof(BAccountR)], employee);
if (String.IsNullOrEmpty(defEmpContact.EMail))
throw new PXException("E-mail is not specified for Employee Contact.");
var sender = TemplateNotificationGenerator.Create(order,
rowNotification.NotificationID.Value);
sender.RefNoteID = order.NoteID;
sender.MailAccountId = rowNotification.NFrom.HasValue ?
rowNotification.NFrom.Value :
PX.Data.EP.MailAccountManager.DefaultMailAccountID;
sender.To = defEmpContact.EMail;
sent |= sender.Send().Any();
}
else
throw new PXException("Customer not found.");
}
else
throw new PXException("Request not found.");
}
catch (Exception Err)
{
sent = false;
sError = Err.Message;
}
if (!sent)
throw new PXException(sError);
}
ts.Complete();
}
}
}
I can get the original code to send an email in my development environment, but my modified code only returns the outer "Failed to send E-mail" error.
Can anyone help point me in the right direction to get my modifications to work?
Because in Acumatica there is one-to-many relationship between RQRequisition and RQRequest, I believe the better approach is to loop through all requests linked to the current requisition and compose a list of requester's emails. After that we can go ahead and send emails to all requesters as part of the Create Orders operation:
public class POOrderEntryExt : PXGraphExtension<POOrderEntry>
{
private bool sendEmailNotification = false;
public bool SendEmailNotification
{
get
{
return sendEmailNotification;
}
set
{
sendEmailNotification = value;
}
}
[PXOverride]
public void Persist(Action del)
{
using (var ts = new PXTransactionScope())
{
if (del != null)
{
del();
}
if (SendEmailNotification)
{
bool sent = false;
string sError = "Failed to send E-mail.";
try
{
Notification rowNotification = PXSelect<Notification,
Where<Notification.name, Equal<Required<Notification.name>>>>
.Select(Base, "PONotification");
if (rowNotification == null)
throw new PXException("Notification Template was not found.");
var order = Base.Document.Current;
var emails = new List<string>();
var requests = PXSelectJoinGroupBy<RQRequest,
InnerJoin<RQRequisitionContent,
On<RQRequest.orderNbr,
Equal<RQRequisitionContent.orderNbr>>>,
Where<RQRequisitionContent.reqNbr,
Equal<Required<RQRequisition.reqNbr>>>,
Aggregate<GroupBy<RQRequest.orderNbr>>>
.Select(Base, order.RQReqNbr);
foreach (RQRequest request in requests)
{
if (request.EmployeeID != null)
{
var requestCache = Base.Caches[typeof(RQRequest)];
requestCache.Current = request;
var emplOrCust = (BAccountR)PXSelectorAttribute
.Select<RQRequest.employeeID>(requestCache, request);
if (emplOrCust != null)
{
var defEmpContact = (Contact)PXSelectorAttribute
.Select<BAccountR.defContactID>(
Base.Caches[typeof(BAccountR)], emplOrCust);
if (!String.IsNullOrEmpty(defEmpContact.EMail) &&
!emails.Contains(defEmpContact.EMail))
{
emails.Add(defEmpContact.EMail);
}
}
}
}
foreach (string email in emails)
{
var sender = TemplateNotificationGenerator.Create(order,
rowNotification.NotificationID.Value);
sender.RefNoteID = order.NoteID;
sender.MailAccountId = rowNotification.NFrom.HasValue ?
rowNotification.NFrom.Value :
PX.Data.EP.MailAccountManager.DefaultMailAccountID;
sender.To = email;
sent |= sender.Send().Any();
}
}
catch (Exception Err)
{
sent = false;
sError = Err.Message;
}
if (!sent)
throw new PXException(sError);
}
ts.Complete();
}
}
}
I found out how to determine the object type from URL for SharePoint on prem:
https://blogs.msdn.microsoft.com/sanjaynarang/2009/04/06/find-sharepoint-object-type-from-url/
But I didn't find anything for SharePoint Online (CSOM).
Is it possible for SharePoint online?
For the most scenarios such as:
folder url, e.g. https://contoso.sharepoint.com//Documents/Forms/AllItems.aspx?RootFolder=%2FDocuments%2FArchive
list item url, e.g. https://contoso.sharepoint.com/Lists/ShoppingCart/DispForm.aspx?ID=9
list/library url, e.g. https://contoso.sharepoint.com/Lists/Announcements
page url, e.g. https://contoso.sharepoint.com/Lists/Announcements/Newsletter.aspx
the following example demonstrates how to determine client object type:
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Web;
using Microsoft.SharePoint.Client;
namespace O365Console
{
static class ClientObjectExtensions
{
public static ClientObject ResolveClientObjectFromUrl(string resourceUrl, ICredentials credentials)
{
ClientObject targetObject = null;
var resourceUri = new Uri(resourceUrl);
using (var rootCtx = new ClientContext(resourceUri.Scheme + Uri.SchemeDelimiter + resourceUri.Host))
{
rootCtx.Credentials = credentials;
var webUrl = Web.WebUrlFromPageUrlDirect(rootCtx, resourceUri);
using (var ctx = new ClientContext(webUrl.ToString()))
{
ctx.Credentials = credentials;
var queryBag = System.Web.HttpUtility.ParseQueryString(resourceUri.Query);
if (queryBag["Id"] != null)
{
var listUrl = string.Join(string.Empty,
resourceUri.Segments.Take(resourceUri.Segments.Length - 1));
var list = ctx.Web.GetList(listUrl);
targetObject = TryRetrieve(() => list.GetItemById(Convert.ToInt32(queryBag["Id"])));
}
else if (queryBag["RootFolder"] != null)
{
var folderUrl = HttpUtility.UrlDecode(queryBag["RootFolder"]);
targetObject = TryRetrieve(() => ctx.Web.GetFolderByServerRelativeUrl(folderUrl));
}
else if (queryBag.Count > 0)
{
throw new Exception("Unsupported query string parameter found");
}
else
{
targetObject = TryRetrieve(() => ctx.Web.GetFileByServerRelativeUrl(resourceUri.AbsolutePath));
if (targetObject == null)
{
targetObject = TryRetrieve(() => ctx.Web.GetList(resourceUri.AbsolutePath),list => list.RootFolder);
if (targetObject == null || ((List)targetObject).RootFolder.ServerRelativeUrl != resourceUri.AbsolutePath)
targetObject = TryRetrieve(() => ctx.Web.GetFolderByServerRelativeUrl(resourceUri.AbsolutePath));
}
}
}
}
return targetObject;
}
private static T TryRetrieve<T>(Func<T> loadMethod, params Expression<Func<T,object>>[] retrievals) where T : ClientObject
{
try
{
var targetObject = loadMethod();
targetObject.Context.Load(targetObject, retrievals);
targetObject.Context.ExecuteQuery();
return targetObject;
}
catch
{
}
return default(T);
}
}
}
Usage
var credentials = GetCredentials(userName, password);
var clientObj = ClientObjectExtensions.ResolveClientObjectFromUrl("https://contoso.sharepoint.com/Lists/Announcements", credentials);
Console.WriteLine(clientObj.GetType().Name);
where
static ICredentials GetCredentials(string userName,string password)
{
var securePassword = new SecureString();
foreach (var c in password)
{
securePassword.AppendChar(c);
}
return new SharePointOnlineCredentials(userName, securePassword);
}
I am using xamarin forms. I want to pick photo from gallery for my iphone app and want to save it in Azure DB. Is there any solution available for xamarin forms. Or Is there any plugin available to deal with Photo, Document, or Audio. Any help is appreciated.
Using dependency service you can take or pick photos from Android / iPhone :-
Please refer to code below and try to implement the similar code:-
This is the interface in PCL:-
public interface IGalleryProvider
{
Task<List<AttachmentMediaFile>> PickPhotoAsync();
Task<List<AttachmentMediaFile>> PickAudioAsync();
Task<List<AttachmentMediaFile>> PickDocumentAsync();
Task<AttachmentMediaFile> PickProfilePhotoAsync();
Task SaveToGalleryAsync(AttachmentMediaFile file);
}
Below is the code using which you can pick or take photos from iPhone only:-
using AssetsLibrary;
using AVFoundation;
using ELCImagePicker;
using Foundation;
using MediaPlayer;
using MobileCoreServices;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UIKit;
[assembly: Xamarin.Forms.Dependency(typeof(GalleryProvider))]
namespace SanketSample.MobileApp.Sample.iOS.Common
{
public class GalleryProvider : IGalleryProvider
{
private TaskCompletionSource<List<AttachmentMediaFile>> _audioPickedTask;
public async Task<List<AttachmentMediaFile>> PickAudioAsync()
{
_audioPickedTask = new TaskCompletionSource<List<AttachmentMediaFile>>();
var picker = new MPMediaPickerController();
ShowViewController(picker);
picker.ItemsPicked += OnAudioPicked;
picker.DidCancel += OnCancel;
var media = await _audioPickedTask.Task;
return media;
}
private void OnCancel(object sender, EventArgs e)
{
var picker = sender as MPMediaPickerController;
picker.DidCancel -= OnCancel;
picker.DismissViewController(true, null);
_audioPickedTask.TrySetResult(new List<AttachmentMediaFile>());
}
private void OnAudioPicked(object sender, ItemsPickedEventArgs e)
{
var media = new List<AttachmentMediaFile>();
var picker = sender as MPMediaPickerController;
picker.ItemsPicked -= OnAudioPicked;
picker.DismissViewController(true, null);
if (e.MediaItemCollection.Items != null)
{
foreach (var item in e.MediaItemCollection.Items)
{
//var vm1 = (new ViewModelLocator()).AttachmentsVM.SelectedAttachments.Add();
if (!item.IsCloudItem)
{
try
{
//var error = new NSError();
//var asset = new AVUrlAsset(item.AssetURL);
//var exporter = new AVAssetExportSession(asset, item.Title);
//exporter.OutputFileType = "com.apple.m4a-audio";
//AVAssetExportSession session = new AVAssetExportSession(asset, "");
//var reader = new AVAssetReader(asset, out error);
//var settings = new NSDictionary();
//Func<byte[]> bytesGetter = e.MediaItemCollection
//TODO item.Title, item.Title SSSanket,
//var _asset = AVAsset.FromUrl(NSUrl.FromFilename(item.AssetURL.ToString()));
//var _exportSession = new AVAssetExportSession(_asset, AVAssetExportSession.PresetPassthrough);
//_exportSession.OutputFileType = AVFileType.Aiff;
// media.Add(new AttachmentMediaFile(item.AssetURL.AbsoluteString, AttachmentMediaFileType.Audio, null , item.Title));
}
catch (Exception ex)
{
// throw ;
}
}
}
}
_audioPickedTask.TrySetResult(media);
}
public async Task<List<AttachmentMediaFile>> PickDocumentAsync()
{
var task = new TaskCompletionSource<List<AttachmentMediaFile>>();
var allowedUTIs = new string[]
{
UTType.UTF8PlainText,
UTType.PlainText,
UTType.RTF,
UTType.Text,
UTType.PDF,
"com.microsoft.word.doc",
"com.microsoft.excel.xls"
};
var pickerMenu = new UIDocumentMenuViewController(allowedUTIs, UIDocumentPickerMode.Open);
pickerMenu.DidPickDocumentPicker += (sender, args) =>
{
args.DocumentPicker.DidPickDocument += (sndr, pArgs) =>
{
var securityEnabled = pArgs.Url.StartAccessingSecurityScopedResource();
NSError err;
var fileCoordinator = new NSFileCoordinator();
var docs = new List<AttachmentMediaFile>();
// Read bytes.
fileCoordinator.CoordinateRead(pArgs.Url, 0, out err, (NSUrl newUrl) =>
{
NSData data = NSData.FromUrl(newUrl);
docs.Add(new AttachmentMediaFile(pArgs.Url.AbsoluteString, AttachmentMediaFileType.Doc, data.ToArray(),null));
task.TrySetResult(docs);
});
};
ShowViewController(args.DocumentPicker);
};
ShowViewController(pickerMenu);
return await task.Task;
}
public async Task<List<AttachmentMediaFile>> PickPhotoAsync()
{
var media = new List<AttachmentMediaFile>();
var picker = ELCImagePickerViewController.Instance;
picker.MaximumImagesCount = 15;
ShowViewController(picker);
await picker.Completion.ContinueWith(result =>
{
picker.BeginInvokeOnMainThread(() =>
{
picker.DismissViewController(true, null);
if (!result.IsCanceled && result.Exception == null)
{
var imageEditor = new ImageEditor();
var items = result.Result as List<AssetResult>;
foreach (var item in items)
{
var bbytes= imageEditor.ResizeImage(item.Image, 1024, 1024);
media.Add(new AttachmentMediaFile(item.Path, AttachmentMediaFileType.Photo, bbytes, item.Name));
}
}
});
});
return media;
}
public async Task<AttachmentMediaFile> PickProfilePhotoAsync()
{
AttachmentMediaFile selectMediaFile = null;
var picker = ELCImagePickerViewController.Instance;
picker.MaximumImagesCount = 1;
ShowViewController(picker);
await picker.Completion.ContinueWith(result =>
{
picker.BeginInvokeOnMainThread(() =>
{
picker.DismissViewController(true, null);
if (!result.IsCanceled && result.Exception == null)
{
var imageEditor = new ImageEditor();
var items = result.Result as List<AssetResult>;
foreach (var item in items)
{
var bbytes = imageEditor.ResizeImage(item.Image, 1024, 1024);
selectMediaFile = new AttachmentMediaFile(item.Path, AttachmentMediaFileType.Photo, bbytes, item.Name);
}
}
});
});
return selectMediaFile;
}
public async Task SaveToGalleryAsync(AttachmentMediaFile file)
{
var bytes = file.GetBytes();
var originalImage = ImageEditor.ImageFromByteArray(bytes);
var library = new ALAssetsLibrary();
var orientation = (ALAssetOrientation)originalImage.Orientation;
var nsUrl = await library.WriteImageToSavedPhotosAlbumAsync(originalImage.CGImage, orientation);
}
private void ShowViewController(UIViewController controller)
{
var topController = UIApplication.SharedApplication.KeyWindow.RootViewController;
while (topController.PresentedViewController != null)
{
topController = topController.PresentedViewController;
}
topController.PresentViewController(controller, true, null);
}
}
}
Below are useful classes :-
public class AttachmentMediaFile
{
private readonly Func<byte[]> _bytesGetter;
public string LocalPath { get; private set; }
public string Name { get; private set; }
public AttachmentMediaFileType Type { get; private set; }
public AttachmentMediaFile(string localPath, AttachmentMediaFileType type, byte[] bytesGetter, string name = null)
{
LocalPath = localPath;
Type = type;
_bytesGetter = () =>
{
return bytesGetter;
};
if (string.IsNullOrEmpty(name))
{
Name = FileNameHelper.PrepareName(localPath);
}
else
{
Name = name;
}
}
public byte[] GetBytes()
{
return _bytesGetter();
}
}
public enum AttachmentMediaFileType
{
Photo = 0,
Audio = 1,
Doc = 2,
Video = 3,
}
public static class FileNameHelper
{
private const string Prefix = "IMG";
public static string PrepareName(string localPath)
{
var name = string.Empty;
if (!string.IsNullOrEmpty(localPath))
{
name = localPath.Split('/').Last();
}
return name;
}
public static string GenerateUniqueFileName(Extension extension)
{
var format = ".jpg";
var fileName = string.Concat(Prefix, '_', DateTime.UtcNow.Ticks, format);
return fileName;
}
public enum Extension
{
JPG
}
}
Now if you want to store your data to Azure Server Table so you are already using Azure mobile service client SDK similarly you need Blob nuget from Azure using which you can save your photos by making blob objects to Azure server :-
use blob helper nuget from manage nuget package install Microsoft.WindowsAzure.Storage.Auth;
Microsoft.WindowsAzure.Storage.Blob;
this and try to implement the code similarly I given bellow:-
using Acr.UserDialogs;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using SanketSample.MobileApp.sample.Business.Azure;
using SanketSample.MobileApp.sample.Business.Interfaces;
using SanketSample.MobileApp.sample.Models;
using SanketSample.MobileApp.sample.Models.AzureTables;
using SanketSample.MobileApp.sample.Models.Media;
using SanketSample.MobileApp.sample.Utils;
using Xamarin.Forms;
namespace SanketSample.MobileApp.Sanket.Common.Media
{
public class BlobHelper
{
private const string ContainerName = "attachments";
private Dictionary<string, TaskCompletionSource<bool>> _tasks;
private IHttpService _httpservice { get; set; }
#region Singleton Implementation
private static readonly Lazy<BlobHelper> lazyInstance = new Lazy<BlobHelper>(() => new BlobHelper(), true);
private BlobHelper()
{
_tasks = new Dictionary<string, TaskCompletionSource<bool>>();
}
public static BlobHelper Instance
{
get { return lazyInstance.Value; }`enter code here`
}
#endregion Singleton Implementation
public async Task UploadAttachments(IList<AttachmentFile> attachments, long associatedRecordId, string category)
{
foreach (var attachment in attachments)
{
await UploadAttachment(attachment, associatedRecordId, category);
}
}
public async Task UploadAttachment(AttachmentFile attachment, long associatedRecordId, string category)
{
try
{
CommonHelper commonHelper = new CommonHelper();
attachment.ContainerName = ContainerName;
attachment.AssociatedRecordId = associatedRecordId;
//attachment.RecordId = commonHelper.GenerateRecordId();
if (attachment.FileExtension == null)
{
attachment.FileExtension = ConvertType(attachment.MediaFile);
}
attachment.Category = category;
var taskCompletionSource = new TaskCompletionSource<bool>();
if (!_tasks.ContainsKey(attachment.Name))
{ _tasks.Add(attachment.Name, taskCompletionSource); }
else
{
_tasks[attachment.Name] = taskCompletionSource;
}
// _tasks.Add(attachment.Name, taskCompletionSource);
var attachmentsTableOnline = AzureServiceProvider.Instance.GetRemoteTable<AttachmentFile>();
if (CheckInternetConnection.IsConnected())
{
await attachmentsTableOnline.InsertAsync(attachment);
}
var attachmentsTableOffline = AzureServiceProvider.Instance.GetLocalTable<AttachmentFile>();
await attachmentsTableOffline.InsertAsync(attachment);
if (!string.IsNullOrEmpty(attachment.SasQueryString))
{
var credentials = new StorageCredentials(attachment.SasQueryString);
var imageUri = new Uri(attachment.Uri);
var container = new CloudBlobContainer(new Uri(string.Format("https://{0}/{1}",
imageUri.Host, attachment.ContainerName)), credentials);
var blobFromSASCredential = container.GetBlockBlobReference(attachment.Name);
try
{
var bytes = attachment.MediaFile.GetBytes();
await blobFromSASCredential.UploadFromByteArrayAsync(bytes, 0, bytes.Length);
if (CheckInternetConnection.IsConnected())
{
await attachmentsTableOnline.UpdateAsync(attachment);
}
await attachmentsTableOffline.UpdateAsync(attachment);
taskCompletionSource.TrySetResult(true);
}
catch (Microsoft.WindowsAzure.Storage.StorageException ex)
{
// Throws from UploadFromByteArrayAsync, but image uploaded.
System.Diagnostics.Debug.WriteLine($"BlobHelper: {ex}");
taskCompletionSource.TrySetResult(true);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"BlobHelper: {ex}");
taskCompletionSource.TrySetResult(false);
}
}
}
catch (Exception ca)
{
//throw ca;
}
}
/// <summary>
/// Downloads Blob Data boject and returns the Byts[] data
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
public async Task<byte[]> DownloadAttachment(AttachmentFile file)
{
byte[] bytes = null;
var fileContainer = file.Uri.Replace(file.Name, string.Empty);
var container = new CloudBlobContainer(new Uri(fileContainer));
var blob = container.GetBlockBlobReference(file.Name);
using (var stream = new MemoryStream())
{
var isExist = await blob.ExistsAsync();
if (isExist)
{
await blob.DownloadToStreamAsync(stream);
bytes = stream.ToArray();
}
}
return bytes;
}
/// <summary>
/// Updates the Attachments Byts in the Azure Local Tables.
/// </summary>
/// <param name="AttachmentFileRecordId">Attachments Byte[] Data.</param>
/// <returns></returns>
public async Task<byte[]> DownloadAttachmentFileDetails(long? AttachmentFileRecordId, IHttpService service)
{
_httpservice = service;
try
{
ResponseWrapper<AttachmentFileDetail> result = new ResponseWrapper<AttachmentFileDetail>();
if (AttachmentFileRecordId != null)
{
var request = Constants.API_BASE_URL + string.Format(Constants.API_ATTACHMENTS_PARAMETERS, AttachmentFileRecordId);
var response = await _httpservice.SendRequestAsync(HttpMethod.Get, request);
result.Status = response.Status;
if (response.IsSuccess)
{
result.Result = JsonConvert.DeserializeObject<AttachmentFileDetail>(response.Result);
if (result.Result == null)
{
result.Status = System.Net.HttpStatusCode.InternalServerError;
}
else
{
var output = result.Result;
var data = new List<AttachmentFileDetail>() { output };
await AzureServiceProvider.Instance.DatabaseService.InsertDataToLocalDB<AttachmentFileDetail>(data);
return result.Result.FileByteArray;
}
}
}
}
catch (Exception ex)
{
////throw ex;
}
finally
{
}
return null;
}
private string ConvertType(AttachmentMediaFile file)
{
switch (file.Type)
{
case AttachmentMediaFileType.Doc:
return "doc";
case AttachmentMediaFileType.Audio:
return "mp3";
}
return "jpeg";
}
}
}
media plugin on github
works pretty well for me.
I am using Servicestack version 4.0.52.0 and ServiceStack.Api.Swagger 4.0.0.0
I have defined all routes with variable route parameter in serviceStack application host derived class(AppHostBase):Configure method
public class AppHost : AppHostBase
{
public override void Configure(Funq.Container container)
{
//Infrastructure code goes here
....
//Add routes
Routes.Add<TestRequestDTO>("/{MyApplicationKey}/TestOperation/", "GET")
}
}
Although these routes are visible in ServiceStack metadata page, swagger UI does not show routes begining with MyApplicationKey; Digging deeper into codebase of we found the issue in ServiceStack.Api.Swagger.SwaggerResourcesService.Get method:
[AddHeader(DefaultContentType = MimeTypes.Json)]
[DefaultRequest(typeof(SwaggerResources))]
[Restrict(VisibilityTo = RequestAttributes.None)]
public class SwaggerResourcesService : Service
{
private readonly Regex resourcePathCleanerRegex = new Regex(#"/[^\/\{]*", RegexOptions.Compiled);
internal static Regex resourceFilterRegex;
internal static Action<SwaggerResourcesResponse> ResourcesResponseFilter { get; set; }
internal const string RESOURCE_PATH = "/resource";
public object Get(SwaggerResources request)
{
var basePath = base.Request.GetBaseUrl();
var result = new SwaggerResourcesResponse
{
BasePath = basePath,
Apis = new List<SwaggerResourceRef>(),
ApiVersion = HostContext.Config.ApiVersion,
Info = new SwaggerInfo
{
Title = HostContext.ServiceName,
}
};
var operations = HostContext.Metadata;
var allTypes = operations.GetAllOperationTypes();
var allOperationNames = operations.GetAllOperationNames();
foreach (var operationName in allOperationNames)
{
if (resourceFilterRegex != null && !resourceFilterRegex.IsMatch(operationName)) continue;
var name = operationName;
var operationType = allTypes.FirstOrDefault(x => x.Name == name);
if (operationType == null) continue;
if (operationType == typeof(SwaggerResources) || operationType == typeof(SwaggerResource))
continue;
if (!operations.IsVisible(Request, Format.Json, operationName)) continue;
CreateRestPaths(result.Apis, operationType, operationName);
}
result.Apis = result.Apis.OrderBy(a => a.Path).ToList();
if (ResourcesResponseFilter != null)
ResourcesResponseFilter(result);
return new HttpResult(result) {
ResultScope = () => JsConfig.With(includeNullValues:false)
};
}
protected void CreateRestPaths(List<SwaggerResourceRef> apis, Type operationType, string operationName)
{
var map = HostContext.ServiceController.RestPathMap;
var feature = HostContext.GetPlugin<SwaggerFeature>();
var paths = new List<string>();
foreach (var key in map.Keys)
{
paths.AddRange(map[key].Where(x => x.RequestType == operationType).Select(t => **resourcePathCleanerRegex.Match**(t.Path).Value));
}
......
}
Any hints on how we can fix the issue? Is there any way to tell value of MyApplicationKey to Swagger API when it is doing resource listing?
There is an opportunity to modify the Swagger Response by specifying a custom ResourcesResponseFilter when you register the SwaggerFeature plugin, e.g:
Plugins.Add(new SwaggerFeature {
ResourceFilterPattern = #"/[^\/\{]*",
ResourcesResponseFilter = response => ...
});
Which will let you modify the Swagger Response that the Swagger UI receives.