Base solution for your next web application

Activities of "amayorquin"

Question

Hi,

I have a problem when I use social login to authenticate users (Using different database for each tenant), I can register the user without problems, but when I logout and try to login again, it redirects to the register page. I checked the code and there is a function that try to find possible tenants for that login, but the problem is that is searching in the host database and the user is registered in a tenant database.

I guess I have to pass the tenant name to make this work. Is there a reason you make this in that way? Or is an error?

Thanks in advance!

Hi, I have the same error in the last version, I'm using different database per tenant. Did you fix it?

Answer

Thanks for your reply,

Actually I'm not change anything yet in code, I just downloaded the ABP and started to check and debug the code.

When I try Login with an external account previously registered (Google), this method is called (tenancyName is empty):

[UnitOfWork]
        public virtual async Task<ActionResult> ExternalLoginCallback(string returnUrl, string tenancyName = "")
        {
            var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
            if (loginInfo == null)
            {
                return RedirectToAction("Login");
            }

            //Try to find tenancy name
            if (tenancyName.IsNullOrEmpty())
            {
                var tenants = await FindPossibleTenantsOfUserAsync(loginInfo.Login);
                switch (tenants.Count)
                {
                    case 0:
                        return await RegisterView(loginInfo);
                    case 1:
                        tenancyName = tenants[0].TenancyName;
                        break;
                    default:
                        return View("TenantSelection", new TenantSelectionViewModel
                        {
                            Action = Url.Action("ExternalLoginCallback", "Account", new { returnUrl }),
                            Tenants = tenants.MapTo<List<TenantSelectionViewModel.TenantInfo>>()
                        });
                }
            }

            var loginResult = await _logInManager.LoginAsync(loginInfo.Login, tenancyName);

            switch (loginResult.Result)
            {
                case AbpLoginResultType.Success:
                    await SignInAsync(loginResult.User, loginResult.Identity, false);

                    if (string.IsNullOrWhiteSpace(returnUrl))
                    {
                        returnUrl = Url.Action("Index", "Home");
                    }

                    return Redirect(returnUrl);
                case AbpLoginResultType.UnknownExternalLogin:
                    return await RegisterView(loginInfo, tenancyName);
                default:
                    throw CreateExceptionForFailedLoginAttempt(loginResult.Result, loginInfo.Email ?? loginInfo.DefaultUserName, tenancyName);
            }
        }

The method that is called to find the possible tenants is the following:

[UnitOfWork]
        protected virtual async Task<List<Tenant>> FindPossibleTenantsOfUserAsync(UserLoginInfo login)
        {
            List<User> allUsers;
            using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.MayHaveTenant))
            {
                allUsers = await _userManager.FindAllAsync(login);
            }

            return allUsers
                .Where(u => u.TenantId != null)
                .Select(u => AsyncHelper.RunSync(() => _tenantManager.FindByIdAsync(u.TenantId.Value)))
                .ToList();
        }

but like I said before, looks like is searching only in the host database

Answer

Thank you very much. This is how I was thinking to solve it, but I had the doubt if you did this on purpose. Thanks again

Hi,

Can you help me, please, I created a Custom Repository to call a stored procedure, and the call to the custom repository is working fine, but I'm getting an error when I test calling the API.

This is my code

Interface in Core project

public interface ILayerAppService : IApplicationService
    {
        Task<ListResultDto<LayerListDto>> GetPersonLayers();
    }

Repository in

public class LayerRepository : CorgeeZeroRepositoryBase<Layer>, ILayerRepository
    {
        public LayerRepository(IDbContextProvider<CorgeeZeroDbContext> dbContextProvider)
        : base(dbContextProvider)
        {
        }

        public List<Layer> GetPersonLayers(long personId)
        {
           List<Layer> layers = new List<Layer>();

            using (var db = GetDbContext())
            {
                db.Database.Initialize(force: false);

                var cmd = db.Database.Connection.CreateCommand();
                cmd.CommandText = "[dbo].[GetPersonLayers]";
                cmd.CommandType = CommandType.StoredProcedure;

                cmd.Parameters.Add(new SqlParameter { ParameterName = "@PersonId", Value = personId, SqlValue = personId,   SqlDbType = SqlDbType.Int, Direction = ParameterDirection.Input });

                try
                {
                    db.Database.Connection.Open();

                    using (var reader = cmd.ExecuteReader())
                    {
                        layers = ((IObjectContextAdapter)db).ObjectContext.Translate<Layer>(reader).ToList();
                    }
                }
                catch (Exception ex)
                {
                    throw ex;
                }
                finally
                {
                    db.Database.Connection.Close();
                }
                return layers;
            }
        }
    }

And the AppService:

public class LayerAppService : CorgeeZeroAppServiceBase, ILayerAppService
    {
        private readonly IRepository<Layer> _layerRepository;
        private readonly ILayerRepository _customLayerRepository;

        public LayerAppService(IRepository<Layer> layerRepository, ILayerRepository customLayerRepository)
        {
            _layerRepository = layerRepository;
            _customLayerRepository = customLayerRepository;
        }

       public async Task<ListResultDto<LayerListDto>> GetPersonLayers()
        {
            var currentUser = await this.GetCurrentUserAsync();
            
            var layers = _customLayerRepository.GetPersonLayers(currentUser.Id);

            var mapping = new ListResultDto<LayerListDto>(layers.MapTo<List<LayerListDto>>());

            return mapping;
        }
    }

I'm getting the results correctly in the mapping var , but for some reason the API is returning an error:

"An internal error occurred during your request!"

Thank you very much for your responses. Finally I changed the way I call the stored procedure and works now.

I changed to this:

var  layers = Context.Database.SqlQuery<Layer>("GetPersonLayersBySecurityGroups @PersonId",
                new SqlParameter("@PersonId", personId)).ToList();

Hi,

I'm trying to add a new method to the AccountController in the API project but is not working, looks like the controller just allow one method, I created a different controller and is the same thing. If I rename the Authenticate method to anything else I am still can access the Authenticate method in postman, seems that is the default route and just allow one method. When I added a new method just throw an error but nothing is registered in the error log, can you help me, please. I don't know if I missing something. Thanks in advance

Hi, thank you for you response.

I just added a new dummy method in the api AccountController:

[Route("api/Account/Register")]
        [HttpPost]
        public AjaxResponse Register()
        {
            return new AjaxResponse(true);
        }

and just added the route in the Authenticate action:

[Route("api/Account/Authenticate")]

The Authenticate action is working, but the when I hit to Register action is calling the Authenticate action, even if I just call the api like this: <a class="postlink" href="http://localhost:6634/api/Account">http://localhost:6634/api/Account</a> is responding the Authenticate method

Those are the only changes I made. I don't know if I have to configurate something else.

Thanks in advance

Hi,

I'm using the OData sample to create a new Web API application using ABP. I just made some changes.

In the AbpODataDemoWebApiModule.cs, I changed this:

[DependsOn(typeof(AbpWebApiODataModule))]
    public class AbpODataDemoWebApiModule : AbpModule
    {
        public override void PreInitialize()
        {
            var builder = Configuration.Modules.AbpWebApiOData().ODataModelBuilder;

            //Configure your entities here...
            builder.EntitySet<Person>("Persons");

            //TODO: Remove after ABP v0.11.2.0 upgrade
            Configuration.Validation.IgnoredTypes.AddIfNotContains(typeof(Type));
            Configuration.Validation.IgnoredTypes.AddIfNotContains(typeof(Delta));
        }

        public override void Initialize()
        {
            IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
        }
    }

for this:

[DependsOn(typeof(AbpWebApiModule))]
    public class AbpODataDemoWebApiModule : AbpModule
    {
        public override void PreInitialize()
        {
         Configuration.Modules.AbpWebApi().HttpConfiguration.MapHttpAttributeRoutes();
        }

        public override void Initialize()
        {
            IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
        }
    }

In the AbpODataDemoWebModule.cs, I just register the Routes, and I created a new class for the routes:

public override void Initialize()
        {
            IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());

            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
public static class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
                );
        }
    }

and finally in the controller changed like this:

public class PersonsController : AbpApiController
    {
        private readonly IRepository<Phone> _phoneRepository;
        private readonly IRepository<Person> _personRepository;

        public PersonsController(
            IRepository<Person> personRepository,
            IRepository<Phone> phoneRepository)
  
        {
            _personRepository = personRepository;
            _phoneRepository = phoneRepository;
        }

        [Route("api/Persons/GetPersons")]
        [HttpGet]
        public List<Person> GetPersons()
        {
            var persons = _personRepository.GetAll().ToList();
            return persons;
        }
}

But when I call the method via Postman I'm getting an exception:

The ObjectContext instance has been disposed and can no longer be used for operations that require a connection

Here is the complete exception:

{ "message": "An error has occurred.", "exceptionMessage": "The 'ObjectContent1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.", "exceptionType": "System.InvalidOperationException", "stackTrace": null, "innerException": { "message": "An error has occurred.", "exceptionMessage": "Error getting value from 'Phones' on 'System.Data.Entity.DynamicProxies.Person_E0D0F5809A10CCF521F5B0560F9B6295EB6C1DAE95890460F8EC9161EADC2F00'.", "exceptionType": "Newtonsoft.Json.JsonSerializationException", "stackTrace": " at Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object target)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CalculatePropertyValues(JsonWriter writer, Object value, JsonContainerContract contract, JsonProperty member, JsonProperty property, JsonContract& memberContract, Object& memberValue)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)\r\n at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType)\r\n at System.Net.Http.Formatting.BaseJsonMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, Encoding effectiveEncoding)\r\n at System.Net.Http.Formatting.JsonMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, Encoding effectiveEncoding)\r\n at System.Net.Http.Formatting.BaseJsonMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content)\r\n at System.Net.Http.Formatting.BaseJsonMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()", "innerException": { "message": "An error has occurred.", "exceptionMessage": "The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.", "exceptionType": "System.ObjectDisposedException", "stackTrace": " at System.Data.Entity.Core.Objects.ObjectContext.get_Connection()\r\n at System.Data.Entity.Core.Objects.ObjectQuery1.GetResults(Nullable1 forMergeOption)\r\n at System.Data.Entity.Core.Objects.ObjectQuery1.Execute(MergeOption mergeOption)\r\n at System.Data.Entity.Core.Objects.DataClasses.EntityCollection1.Load(List1 collection, MergeOption mergeOption)\r\n at System.Data.Entity.Core.Objects.DataClasses.EntityCollection1.Load(MergeOption mergeOption)\r\n at System.Data.Entity.Core.Objects.DataClasses.RelatedEnd.DeferredLoad()\r\n at System.Data.Entity.Core.Objects.Internal.LazyLoadBehavior.LoadProperty[TItem](TItem propertyValue, String relationshipName, String targetRoleName, Boolean mustBeNull, Object wrapperObject)\r\n at System.Data.Entity.Core.Objects.Internal.LazyLoadBehavior.<>c__DisplayClass72.<GetInterceptorDelegate>b__1(TProxy proxy, TItem item)\r\n at System.Data.Entity.DynamicProxies.Person_E0D0F5809A10CCF521F5B0560F9B6295EB6C1DAE95890460F8EC9161EADC2F00.get_Phones()\r\n at GetPhones(Object )\r\n at Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object target)" } } }

Hi,

Can you help me please, I'mt trying to use hangfire to set a recurring job to run over all tenants. The job works fine when I use the sample you prepared: <a class="postlink" href="https://gist.github.com/hikalkan/d543b6ded179d1f05227f49bc70e2fbe">https://gist.github.com/hikalkan/d543b6 ... 9bc70e2fbe</a>

But when I tried to use UOW I,m getting an error. This is my code:

In Startup.cs in configuration method I just added this line at the end:

app.UseHangfireDashboard("/hangfire", new DashboardOptions());

In the web module I added this in PostInitialize Methdod

var jobs = IocManager.Resolve<BackgroundJobs>();
RecurringJob.AddOrUpdate("ReplyUpdates", () => jobs.ReplyUpdates(), Cron.MinuteInterval(5));

And my BackgroundJobs class:

public class BackgroundJobs : ITransientDependency
    {
        private readonly ILogger _logger;
        private readonly IRepository<Tenant> _tenantRepository;
        private readonly IUnitOfWorkManager _unitOfWorkManager;

        public BackgroundJobs(ILogger logger,
            IRepository<Tenant> tenantRepository,
            IUnitOfWorkManager unitOfWorkManager)
        {
            _logger = logger;
            _tenantRepository = tenantRepository;
            _unitOfWorkManager = unitOfWorkManager;
        }

        [UnitOfWork]
        public virtual void ReplyUpdates()
        {
            var tenants = _tenantRepository.GetAllList(t => t.ConnectionString != null && t.ConnectionString != "");
            for (int i = 0; i < tenants.Count; i++)
            {
                var tenantId = tenants[i].Id;
                using (_unitOfWorkManager.Current.SetTenantId(tenantId))
                {
                    _logger.Info("MyJob1Class is working!");
                }
            }
            
        }
    }

When I added the [UnitOfWork] attributte I'm getting the following error, in the hangfire dashboard:

Could not load file or assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.

If I not use the [UnitOfWork] attribute the Job runs fine, but _unitOfWorkManager.Current is null

Can you help me please

Thanks in advance

Showing 1 to 10 of 10 entries