My app has 2 databases, Db1 (has table dbo.Student) and Db2 (has table dbo.School). I created 2 AppService to access them
When I tried to get data from both of them, It only uses the connection to Db1 (Could not found table dbo.School in the current context). So how can I get the data from Db1 and Db2 at the same time
private readonly IStudentAppService _studentAppService;
private readonly ISchoolAppService _schoolAppService;
public BranchAccountController(IStudentAppService studentAppService,
ISchoolAppService schoolAppService)
{
_studentAppService = studentAppService;
_schoolAppService = schoolAppService;
}
public async Task<PartialViewResult> GetStudent(int? id)
{
//Repository 1 (Database 1)
var student = await _studentAppService.GetStudentForEdit(new NullableIdDto { Id = id });
//Repository 2 (Database 2)
var school = await _schoolAppService.GetSchoolList();
//bla bla
}
Update 1:
I tried to get the school before student and face the below error:
The transaction passed in is not associated with the current
connection. Only transactions associated with the current connection
may be used.
You have to Begin separate units of work:
public async Task<PartialViewResult> GetStudent(int? id)
{
List<SchoolDto> schools;
StudentDto student;
using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress))
{
// Repository 1 (Database 1)
student = await _studentAppService.GetStudentForEdit(new NullableIdDto { Id = id });
uow.Complete();
}
using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress))
{
// Repository 2 (Database 2)
schools = await _schoolAppService.GetSchoolList();
uow.Complete();
}
// ...
}
Related
I have an web front end calling an ASP Web Api 2 backend. Authentication is managed with ASP Identity. For some of the controllers I'm creating I need to know the user making the call. I don't want to have to create some weird model to pass in including the user's identity (which I don't even store in the client).
All calls to the API are authorized using a bearer token, my thought is the controller should be able to determine the user context based on this but I do not know how to implement. I have searched but I don't know what I'm searching for exactly and haven't found anything relevant. I'm going for something like...
public async Task<IHttpActionResult> Post(ApplicationIdentity identity, WalkthroughModel data)
Update
I found the below which looked very promising... but the value is always null! My controller inherits from ApiController and has an Authorize header.
var userid = User.Identity.GetUserId();
Update 2
I have also tried all of the solutions in Get the current user, within an ApiController action, without passing the userID as a parameter but none work. No matter what I am getting an Identity that is valid and auth'd, but has a null UserID
Update 3
Here's where I'm at now.
[Authorize]
[Route("Email")]
public async Task<IHttpActionResult> Get()
{
var testa = User.Identity.GetType();
var testb = User.Identity.GetUserId();
var testc = User.Identity.AuthenticationType;
var testd = User.Identity.IsAuthenticated;
return Ok();
}
testa = Name: ClaimsIdentity,
testb = null,
testc = Bearer,
testd = true
The user is obviously authenticated but I am unable to retrieve their userID.
Update 4
I found an answer, but I'm really unhappy with it...
ClaimsIdentity identity = (ClaimsIdentity)User.Identity;
string username = identity.Claims.First().Value;
That gets me the username without any db calls but it seems very janky and a pain to support in the future. Would love if anyone had a better answer.
What if I need to change what claims are issued down the road? Plus any time I actually need the user's id I have to make a db call to convert username to ID
A common approach is to create a base class for your ApiControllers and take advantage of the ApplicationUserManager to retrieve the information you need. With this approach, you can keep the logic for accessing the user's information in one location and reuse it across your controllers.
public class BaseApiController : ApiController
{
private ApplicationUser _member;
public ApplicationUserManager UserManager
{
get { return HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>(); }
}
public string UserIdentityId
{
get
{
var user = UserManager.FindByName(User.Identity.Name);
return user.Id;
}
}
public ApplicationUser UserRecord
{
get
{
if (_member != null)
{
return _member ;
}
_member = UserManager.FindByEmail(Thread.CurrentPrincipal.Identity.Name);
return _member ;
}
set { _member = value; }
}
}
I use a custom user authentication (I dont use AspIdentity because my existing user table fields was far different from IdentityUser properties) and create ClaimsIdentity passing my table UserID and UserName to validate my bearer token on API calls.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
User user;
try
{
var scope = Autofac.Integration.Owin.OwinContextExtensions.GetAutofacLifetimeScope(context.OwinContext);
_service = scope.Resolve<IUserService>();
user = await _service.FindUserAsync(context.UserName);
if (user?.HashedPassword != Helpers.CustomPasswordHasher.GetHashedPassword(context.Password, user?.Salt))
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
catch (Exception ex)
{
context.SetError("invalid_grant", ex.Message);
return;
}
var properties = new Dictionary<string, string>()
{
{ ClaimTypes.NameIdentifier, user.UserID.ToString() },
{ ClaimTypes.Name, context.UserName }
};
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
properties.ToList().ForEach(c => identity.AddClaim(new Claim(c.Key, c.Value)));
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties(properties));
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(identity);
}
And how I use the ClaimsIdentity to retrieve my User table details on User ApiController Details call.
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("Details")]
public async Task<IHttpActionResult> Details()
{
var user = await _service.GetAsync(RequestContext.Principal.Identity.GetUserId<int>());
var basicDetails = Mapper.Map<User, BasicUserModel>(user);
return Ok(basicDetails);
}
Notice the
ClaimTypes.NameIdentifier = GetUserId() and ClaimTypes.Name = GetUserName()
I have the following code
here is how I add a list of values to session
public ActionResult Add(Product product)
{
if (Session["AddToCart"] == null)
{
Session["AddToCart"] = new List<Product>();
}
var list = (List<Product>)Session["AddToCart"];
list.Add(product);
}
but how to remove a single record when a session contains multiple records. I am trying to pass an Id but it is not removing the record from the session. Here is how I perform the next step.
Public ActionResult Remove(Product product)
{
Product prod=db.Products.Single(x=>x.Id==product.Id);
var list=(List<Product>)Session["AddToCart"];
//Is this the correct approach
list.Remove(prod);
}
The above code doesn't works. Am I correct or is there anything missing plz correct the above code. Thanks.
Try this,
var list=(List<Product>)Session["AddToCart"];
list.RemoveAll(p => p.Id == product.Id);
Your choice of finding the product with the code db.Products.Single(x=>x.Id==product.Id); may not be the same object with the one in the session.
Edit:
Or you can implement IEquatable<Product> interface. In this case your code would work too.
public class Product : IEquatable<Product>
{
public int Id;
public bool Equals(Product prod)
{
return prod.Id == Id;
}
// Rest of the class
}
I'm trying to update a Customer in my database using ASP.NET Web API and Entity Framework 5 code-first, but it's not working. My entities look like this:
public class CustomerModel
{
public int Id { get; set; }
public string Name { get; set; }
// More fields
public ICollection<CustomerTypeModel> CustomerTypes { get; set; }
}
public class CustomerTypeModel
{
public int Id { get; set; }
public string Type { get; set; }
[JsonIgnore]
public ICollection<CustomerModel> Customers { get; set; }
}
Nothing all that special. I've built a web interface where users can add a customer by supplying the name and checking one or more customer types. When hitting the submit button, the data is sent to my Web API method:
public void Put([FromBody]CustomerModel customer)
{
using (var context = new MyContext())
{
context.Customers.Attach(customer);
context.Entry(customer).State = EntityState.Modified;
context.SaveChanges();
}
}
This updates the customer fields, but the related customer types are ignored. The incoming customer object does contain a list of CustomerTypes it should be associated with:
[0] => { Id: 1, Type: "Finance", Customers: Null },
[1] => { Id: 2, Type: "Insurance", Customers: Null }
[2] => { Id: 3, Type: "Electronics", Customers: Null }
But instead of looking at this list and adding/removing associated entities, EF just ignores it. New associations are ignored and existing associations remain even if they should be deleted.
I had a similar problem when inserting a customer into the database, this was fixed when I adjusted the state of these entities to EntityState.Unchanged. Naturally, I tried to apply this same magic fix in my update scenario:
public void Put([FromBody]CustomerModel customer)
{
using (var context = new MyContext())
{
foreach (var customertype in customer.CustomerTypes)
{
context.Entry(customertype).State = EntityState.Unchanged;
}
context.Customers.Attach(customer);
context.Entry(customer).State = EntityState.Modified;
context.SaveChanges();
}
}
But EF keeps displaying the same behavior.
Any ideas on how to fix this? Or should I really just do a manual clear to the list of CustomerTypes and then manually add them?
Thanks in advance.
JP
This is not really solvable by only setting entity states. You must load the customer from the database first including all its current types and then remove types from or add types to the loaded customer according to the updated types collection of the posted customer. Change tracking will do the rest to delete entries from the join table or insert new entries:
public void Put([FromBody]CustomerModel customer)
{
using (var context = new MyContext())
{
var customerInDb = context.Customers.Include(c => c.CustomerTypes)
.Single(c => c.Id == customer.Id);
// Updates the Name property
context.Entry(customerInDb).CurrentValues.SetValues(customer);
// Remove types
foreach (var typeInDb in customerInDb.CustomerTypes.ToList())
if (!customer.CustomerTypes.Any(t => t.Id == typeInDb.Id))
customerInDb.CustomerTypes.Remove(typeInDb);
// Add new types
foreach (var type in customer.CustomerTypes)
if (!customerInDb.CustomerTypes.Any(t => t.Id == type.Id))
{
context.CustomerTypes.Attach(type);
customerInDb.CustomerTypes.Add(type);
}
context.SaveChanges();
}
}
A cleaner solution would be:
public void Put([FromBody]CustomerModel customer)
{
using (var context = new MyContext())
{
var customerInDb = context.Customers.Include(c => c.CustomerTypes)
.Single(c => c.Id == customer.Id);
// Updates the Name property
context.Entry(customerInDb).CurrentValues.SetValues(customer);
// Remove types
customer.CustomerTypes.Clear();
// Add new types
foreach (var type in customer.CustomerTypes)
{
context.CustomerTypes.Attach(type);
customerInDb.CustomerTypes.Add(type);
}
context.SaveChanges();
}
}
I have some custom fields on my User object that I want to access with APEX code in my VisualForce trigger. When I access it from a Formula field I get to use a nifty $User reference like this:
$User.my_prop__c
From APEX I have to query the User object by UserId like this:
[select my_prop__c from User where id = :UserInfo.getUserId()].my_prop__c;
Is there something baked into APEX already that would let me get at the user properties without the SOQL query? If not, does anyone know of a utility class for lazy loading and caching user properties so the overhead is minimal.
I would use something similar to the following code sample. It uses a singleton pattern to statically store the information in memory for the duration of your transaction. It's similar to the lazy loading that twamley proposed but I feel this is a much simpler approach.
Usage 1: UserUtil.CurrentUser.Email;
Usage 2: User someUser = UserUtil.getUser(someUserId);
This will allow you to access the same information on the current user or other users in the system. Notice the queryUsers method just returns a query result. This makes it easy to add and remove fields from your query as it is isolated in its own method keeping things simple.
Note: that this code pulls in all users when used. Most orgs do not have multiple hundreds of users so heap size shouldn't be a concern. But if it is you can just modify the queryUsers() method to only return active users or filter down based on other criteria.
public class UserUtil {
//Protected Members
private static final UserUtil instance = new UserUtil();
private Map<Id, User> mapUsers;
//Properties
public static User CurrentUser {
get { return getUser(UserInfo.getUserId()); }
}
//Constructor
private UserUtil() {
mapUsers = new Map<Id, User>(queryUsers());
}
//Public Methods
public static User getUser(Id userId) {
if (instance.mapUsers.containsKey(userId)) {
return instance.mapUsers.get(userId);
}
else {
throw new InvalidUserIdException('Unable to locate user id: ' + userId);
}
}
//Private Methods
private List<User> queryUsers() {
return [SELECT
Id
, Name
, UserName
, Email
, Alias
FROM
User];
}
//Internal Classes
public class InvalidUserIdException extends Exception {}
}
I wrote my own utility class. I'm still interested in better techniques though.
This utility class lazy loads when the first property is accessed. Update_Closed_Won_Opportunities__c and Set_Opportunities_to_Closed_Won__c are my custom fields on the User object (visible only to System Administrators so people can't upgrade their permissions).
public with sharing class MyUserInfo {
private Id userId;
private User myUser; // Hold onto the user object once we've loaded it
// Default constructor uses the active user id
public MyUserInfo() {
userId = UserInfo.getUserId();
}
// Secondary constructor accepts a user id as a parameter
public MyUserInfo(Id someOtherUserId) {
userId = someOtherUserId;
}
// Only called one time when we first need it so grab all of the custom fields now
private void LazyLoadUser() {
System.AssertNotEquals(null, userId);
myUser = [
SELECT Update_Closed_Won_Opportunities__c, Set_Opportunities_To_Closed_Won__c
FROM User
WHERE id = :userId
];
System.AssertNotEquals(null, myUser, 'Unable to load user with id ' + userId); // could return defaults instead
}
// Getters (be sure to include each field in the SOQL of LazyLoadUser)
public boolean UpdateClosedWonOpportunities { get {
if (myUser == null) LazyLoadUser();
return myUser.Update_Closed_Won_Opportunities__c;
} }
public boolean SetOpportunitiesToClosedWon { get {
if (myUser == null) LazyLoadUser();
return myUser.Set_Opportunities_To_Closed_Won__c;
} }
}
Here is my trigger utilizing that class. The first line myUserInfo = new MyUserInfo(); doesn't run any SOQL. That won't happen until the first custom get property is used. Subsequent calls don't need SOQL.
trigger LockClosedOpportunity on Opportunity (before update) {
MyUserInfo myUserInfo = new MyUserInfo();
for (Opportunity o : trigger.old)
{
if (!myUserInfo.UpdateClosedWonOpportunities && o.StageName == 'Closed Won')
trigger.newMap.get(o.Id).addError('You do not have permission to change an Opportunity after it has been set to Closed Won.');
}
for (Opportunity o : trigger.new)
{
if ( !myUserInfo.SetOpportunitiesToClosedWon && o.StageName == 'Closed Won' && trigger.oldMap.get(o.Id).StageName != 'Closed Won' )
o.addError('You do not have permission to set an Opportunity to Closed Won.');
}
}
It reads similar to $User in formulas and I don't have to worry about tacking on multiple SOQL calls when one (or zero) suffices.
I have a manually invoked process which is tied to the account entity. This process has a number of steps. One of the first steps is to create a task and assign it to someone. It's expected that this person will add some notes and complete the task.
Further down the process, I have a step to create a service case. After this is created, I want to copy the note(s) from the task above to the newly created service case.
I have created a custom workflow activity to try and accomplish this. I have gotten as far as deploying it and using it within my process without any errors and it does copy content into the notes field of the service case, however it copies the title of the task, not the note content and I can't quite fathom out why.
public class CopyNotes : CodeActivity
{
protected override void Execute(CodeActivityContext executionContext)
{
//Create the context
IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
//get the notes associated with the source entity
Guid copyFromId = CopyFrom.Get(executionContext).Id;
Guid copyToId = CopyTo.Get(executionContext).Id;
EntityCollection copyFromNotes = RetrieveNotes(service, copyFromId);
if (copyFromNotes.Entities.Any())
{
foreach (Entity e in copyFromNotes.Entities)
{
Entity newNote = new Entity("annotation");
newNote.Attributes["subject"] = e.Attributes["subject"];
newNote.Attributes["notetext"] = e.Attributes["notetext"];
newNote.Attributes["objectid"] = new EntityReference() { Id = copyToId, LogicalName = CopyTo.Get(executionContext).LogicalName };
}
}
}
private EntityCollection RetrieveNotes(IOrganizationService service, Guid relatedObject)
{
ConditionExpression condition = new ConditionExpression();
condition.AttributeName = "objectid";
condition.Operator = ConditionOperator.Equal;
condition.Values.Add(relatedObject.ToString());
ColumnSet columns = new ColumnSet("subject", "notetext");
QueryExpression query = new QueryExpression();
query.ColumnSet = columns;
query.EntityName = "annotation";
query.Criteria.AddCondition(condition);
EntityCollection results = service.RetrieveMultiple(query);
return results;
}
[RequiredArgument]
[ReferenceTarget("task")]
[Input("Copy notes from item")]
public InArgument<EntityReference> CopyFrom { get; set; }
[RequiredArgument]
[ReferenceTarget("incident")]
[Input("Copy notes to item")]
public InArgument<EntityReference> CopyTo { get; set; }
}
I think you need to actually create the newNote after defining it.
foreach (Entity e in copyFromNotes.Entities)
{
Entity newNote = new Entity("annotation");
newNote.Attributes["subject"] = e.Attributes["subject"];
newNote.Attributes["notetext"] = e.Attributes["notetext"];
newNote.Attributes["objectid"] = new EntityReference() { Id = copyToId, LogicalName = CopyTo.Get(executionContext).LogicalName };
service.Create(newNote);
}
Once I did that your code worked just fine creating a new note with both the title and note text.
You could do this with a standard workflow, if Async creation is fast enough.
Andreas