Base solution for your next web application
Open Closed

New File Upload ASP.NET Core API method (clone of user upload) not working - 401 unauthorized error #7419


User avatar
0
schlarmanp created

New File Upload ASP.NET Core API method (clone of user upload) not working - 401 unauthorized error

This is v6.9 angular/asp.net core template.

  1. I have a requirement to upload/import a spreadsheet and load the data into the database.
  2. The pattern follows nicely the same pattern implemented in your framework to upload users.
  3. I cloned the modules related to user upload from Excel and transformed into my custom modules:

**asp.net core side solution:

.Application project:

  1. Cloned and modified all related class files under /Authorization/Users/Importing to /CashSheets/Importing a. Dto/ImportUserDto.cs to Dto/ImportCashSheetTransactionDto.cs b. /IInvalidUserExporter.cs to /IInvalidCashSheetTransactionExporter.cs c. /ImportUsersToExcelJob.cs to /ImportCashSheetTransactionsFromExcelJob.cs d. /InvalidUserExporter.cs to /InvalidCashSheetTransactionExporter.cs e. /UserListExcelDataReader.cs to /CashSheetTransactionListExcelDataReader.cs f. /IUserListExcelDataReader.cs to /ICashSheetTransactionListExcelDataReader.cs

.Shared project:

  1. Cloned and modified /Authorization/Users/Dto/ImportUsersFromExcelJobArgs.cs to /CashSheets/Dto/ImportCashSheetTransactionsFromExcelJobArgs.cs

.Web.Core project:

  1. Cloned and modified /Controllers/UsersControllerBase.cs to /Controllers/CashSheetsControllerBase.cs

.Web.Host project:

  1. Cloned and modified /Controllers/UsersController.cs to /Controllers/CashSheetsController.cs

**CashSheetsControllerBase.cs:

using System; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Abp.IO.Extensions; using Abp.UI; using Abp.Web.Models; using SchlarmanConsulting.OneTAFUnify.Authorization.Users.Dto; using SchlarmanConsulting.OneTAFUnify.Storage; using Abp.BackgroundJobs; using SchlarmanConsulting.OneTAFUnify.Authorization; using Abp.AspNetCore.Mvc.Authorization; using Abp.Authorization; using Abp.Runtime.Session; using GraphQL; using Microsoft.AspNetCore.Authorization; using SchlarmanConsulting.OneTAFUnify.CashSheets; using SchlarmanConsulting.OneTAFUnify.CashSheets.Dto; using SchlarmanConsulting.OneTAFUnify.CashSheets.Importing;

namespace SchlarmanConsulting.OneTAFUnify.Web.Controllers { public abstract class CashSheetsControllerBase : OneTAFUnifyControllerBase { protected readonly IBinaryObjectManager BinaryObjectManager; protected readonly IBackgroundJobManager BackgroundJobManager;

    protected CashSheetsControllerBase(
        IBinaryObjectManager binaryObjectManager,
        IBackgroundJobManager backgroundJobManager)
    {
        BinaryObjectManager = binaryObjectManager;
        BackgroundJobManager = backgroundJobManager;
    }

    //[AllowAnonymous] //currently only way this works, but then can't get abp user object needed for transactions :\
    //[AbpAllowAnonymous] //this doesn't work either
    [HttpPost]
    [AbpAuthorize,
     AbpMvcAuthorize,
     AbpMvcAuthorize(AppPermissions.Pages_ProcessCashSheets), 
     AbpMvcAuthorize(AppPermissions.Pages_ProcessCashSheets_CashSheetBatches_Create), 
     AbpMvcAuthorize(AppPermissions.Pages_ProcessCashSheets_CashSheetBatches_Edit)]
    public async Task<JsonResult> ImportFromExcel()
    {
        try
        {
            var file = Request.Form.Files.First();
            //TODO: parse file name for batch number and org, etc.

            if (file == null)
            {
                throw new UserFriendlyException(L("File_Empty_Error"));
            }

            if (file.Length > 1048576 * 100) //100 MB
            {
                throw new UserFriendlyException(L("File_SizeLimit_Error"));
            }

            byte[] fileBytes;
            using (var stream = file.OpenReadStream())
            {
                fileBytes = stream.GetAllBytes();
            }

            var tenantId = AbpSession.TenantId;
            var fileObject = new BinaryObject(tenantId, fileBytes);
            //set CashSheetBatchID also to assign to args
            var dict = Request.Form.ToDictionary(x => x.Key, x => x.Value.ToString());
            var cashSheetBatchID = Int32.Parse(dict["CashSheetBatchID"]);

            await BinaryObjectManager.SaveAsync(fileObject);

            await BackgroundJobManager.EnqueueAsync<ImportCashSheetTransactionsFromExcelJob, ImportCashSheetTransactionsFromExcelJobArgs>(new ImportCashSheetTransactionsFromExcelJobArgs
            {
                //These fields are required for core operation
                //This is where arg values are set
                TenantId = tenantId,
                BinaryObjectId = fileObject.Id,
                User = AbpSession.ToUserIdentifier(), //think this isn't working because entering anonymously
                //User = objUser,
                //need to also set passed in CashSheetBatchID
                CashSheetBatchID = cashSheetBatchID

            });

            return Json(new AjaxResponse(new { }));
        }
        catch (UserFriendlyException ex)
        {
            return Json(new AjaxResponse(new ErrorInfo(ex.Message)));
        }
    }
}

}

**CashSheetsController.cs:

using Abp.AspNetCore.Mvc.Authorization; using SchlarmanConsulting.OneTAFUnify.Authorization; using SchlarmanConsulting.OneTAFUnify.Storage; using Abp.BackgroundJobs;

namespace SchlarmanConsulting.OneTAFUnify.Web.Controllers { //auth seems to be working fine here [AbpMvcAuthorize(AppPermissions.Pages_ProcessCashSheets_CashSheetBatches_Create), AbpMvcAuthorize(AppPermissions.Pages_ProcessCashSheets_CashSheetBatches_Edit)] public class CashSheetsController : CashSheetsControllerBase { public CashSheetsController(IBinaryObjectManager binaryObjectManager, IBackgroundJobManager backgroundJobManager) : base(binaryObjectManager, backgroundJobManager) { } } }

Angular solution:

Create-or-edit-cashSheetBatch-modal.component.html:

                    <a href="javascript:;" *ngIf="isGranted('Pages.ProcessCashSheets')" class="no-padding">
                        <span>                          
                            <p-fileUpload customUpload="true"
                                            name="ExcelFileUpload"
                                            #ExcelFileUpload
                                            maxFileSize="10000000"
                                            auto="auto"
                                            accept=".csv,.xls,.xlsx"
                                            (uploadHandler)="uploadExcel($event)"
                                            (onError)="onUploadExcelError()"
                                            chooseLabel="{{'ImportFromExcel' | localize}}">
                            </p-fileUpload>
                        </span>
                    </a>

Create-or-edit-cashSheetBatch-modal.component.ts:

//upload import { FileUpload } from 'primeng/fileupload'; import { HttpClient } from '@angular/common/http';

export class CreateOrEditCashSheetBatchModalComponent extends AppComponentBase implements OnInit{

//CONSTRUCTOR
constructor(
    injector: Injector,
    private _cashSheetTransactionsServiceProxy: CashSheetTransactionsServiceProxy, //added for local use to load bank filter dropdown
    private _cashSheetBatchesServiceProxy: CashSheetBatchesServiceProxy,
    public formBuilderStepper:FormBuilder, //stepper visual
    private _httpClient: HttpClient, //for file upload
    private formBuilderCashSheetAccountSummaries: FormBuilder, //for accountsummaries 
    public cashSheetAccountSummariesEditService: CashSheetAccountSummariesEditService //for account summaries
) {
    super(injector);
    this.uploadUrl = AppConsts.remoteServiceBaseUrl + '/CashSheets/ImportFromExcel'; //for file upload
}

//IMPORT TAB DECLARATIONS
//file upload
@ViewChild('ExcelFileUpload') excelFileUpload: FileUpload;
//file upload
uploadUrl: string;

//IMPORT TAB EVENT HANDLERS

uploadExcel(data: { files: File }): void {
    
    const formData: FormData = new FormData();
    const file = data.files[0];
    var cashSheetBatchID: string = "";

    formData.append('file', file, file.name);

    //get cash sheet batch id - this should be created in show
    if (this.cashSheetBatch.id == null) {
        //do nothing, accept initialized value
    } else {
        //cast to string
        cashSheetBatchID=this.cashSheetBatch.id.toString();
    }

    //Append Cash Sheet Batch ID to form object
    formData.append("CashSheetBatchID", cashSheetBatchID);
    //post the request - transfer the Excel file to the API for import/processing
    //TODO: get list of errors back from API - this will populate the new "problems" table to replace
    //the field in the equivalent LP table
    //TODO: Also need to pass in QBBatchSummary start and end date values - remote function will populate
    //based on detail data
    this._httpClient
        .post<any>(this.uploadUrl, formData)
        .pipe(finalize(() => this.excelFileUpload.clear()))
        .subscribe(response => {
            if (response.success) {
                this.notify.success(this.l('ImportCashSheetProcessStart'));
            } else if (response.error != null) {
                this.notify.error(this.l('ImportCashSheetUploadFailed'));
            }
        });
}

onUploadExcelError(): void {
    this.notify.error(this.l('ImportCashSheetUploadFailed'));
}

**So, I have a catch 22:

If I leave the [AllowAnonymous] decorator off CashSheetsControllerBase.ImportFromExcel, then I get a permission error:

Failed to load resource: the server responded with a status of 401 (Unauthorized) [http://localhost:22742/CashSheets/ImportFromExcel] ERROR core.js:15724 HttpErrorResponse {headers: HttpHeaders, status: 401, statusText: "Unauthorized", url: "http://localhost:22742/CashSheets/ImportFromExcel", ok: false, …} core.js:15724

I've tried multiple combinations of [AbpMvcAuthorize] decorator values, and nothing seems to work.

If I set the decorator on CashSheetsControllerBase.ImportFromExcel to [AllowAnonymous], I can use the method without the above permissions error from angular, but the User object is not populated, and I can’t use it further on in processing as I need.

I’ve compared all the modules in the UserUpload process to my cloned modules, and it seems like I’m doing everything the same. I must be missing something. Any thoughts?


9 Answer(s)
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    I think the original implementation has a bug, that is why you are not able to use similar approach. Can you import users via excel file ?

  • User Avatar
    0
    schlarmanp created

    Hi,

    Yes, I can import users (if the input file contains certain data in the exact right format) and also the httppost method/file upload works without permission error. I think there are some other bugs in the user import (it doesn’t return erroneous records correctly, etc.), but the main thing is I need to get past this permission error. Other thoughts on what to try?

    Lastly (but secondary importance), is there a fix for known users import bugs that I can implement without upgrading the template?

  • User Avatar
    0
    schlarmanp created

    Hi again - could you tell me how the abp auth works User method "ImportFromExcel" (of UsersControllerBase.cs) and not my clone of that class/method (CashSheetsControllerBase.cs)? Seems I'm missing some config or decorator somewhere, but I've tried everything I can think of. As you can see, I've also tried a straight [AbpMvcAuthorize] (which just requires a user logon, correct?) which doesn't work either.

    Any help is much appreciated.

    Cheers

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @schlarmanp

    Sorry for my late response, have you solved this problem ?

    Thanks,

  • User Avatar
    0
    schlarmanp created

    Hi @ismcagdas - no, still have not solved this.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Does your request for uploading a file contains an header like this:

    If not, could you add AbpHttpInterceptor as we do in https://github.com/aspnetzero/aspnet-zero-core/blob/dev/angular/src/shared/service-proxies/service-proxy.module.ts#L43 to your custom module ?

  • User Avatar
    0
    schlarmanp created

    Hi,

    The addition of AbpHttpInterceptor references as suggested worked for passing authentication to the upload httppost method. The method can be adorned with [AbpAuthorize] successfullly and once inside the method, I can see the user object is populated. Thank you.

        [HttpPost]
        [AbpMvcAuthorize]
        //[AbpAuthorize,
        // AbpMvcAuthorize,
        // AbpMvcAuthorize(AppPermissions.Pages_ProcessCashSheets), 
        // AbpMvcAuthorize(AppPermissions.Pages_ProcessCashSheets_CashSheetBatches_Create), 
        // AbpMvcAuthorize(AppPermissions.Pages_ProcessCashSheets_CashSheetBatches_Edit)]
        public async Task<JsonResult> ImportFromExcel()
        {
            try
            {
                var file = Request.Form.Files.First();
                //TODO: parse file name for batch number and org, etc.
    
                if (file == null)
                {
                ...
        
    

    User obj is populated:

                ?AbpSession.ToUserIdentifier()
    

    {3@1} TenantId: 1 UserId: 3

    I just have one follow-up question. Once further along inside the method, when I make a call to an app object to interact with (PowerTools generated) entity objects, if the method is adorned with any [AbpAuthorize] tag, an error "Current user did not login to the application!" is thrown.

    CashSheetTransactionAppService.cs:

    namespace SchlarmanConsulting.OneTAFUnify.CashSheets { [AbpAuthorize] //if I remove this everything works public class CashSheetTransactionsAppService : OneTAFUnifyAppServiceBase, ICashSheetTransactionsAppService { private readonly IRepository<CashSheetTransaction> _cashSheetTransactionRepository; private readonly ICashSheetTransactionsExcelExporter _cashSheetTransactionsExcelExporter;

    	  public CashSheetTransactionsAppService(IRepository&lt;CashSheetTransaction&gt; cashSheetTransactionRepository, ICashSheetTransactionsExcelExporter cashSheetTransactionsExcelExporter ) 
    	  {
    		_cashSheetTransactionRepository = cashSheetTransactionRepository;
    		_cashSheetTransactionsExcelExporter = cashSheetTransactionsExcelExporter;
    		
    	  }
    

    ...

    [AbpAuthorize] private async Task Create(CreateOrEditCashSheetTransactionDto input) { var cashSheetTransaction = ObjectMapper.Map<CashSheetTransaction>(input);

            await _cashSheetTransactionRepository.InsertAsync(cashSheetTransaction);
         }
         ...
         
    

    When I attempt to call this further along after the httppost method entry,

          //Create insertable object
            var cashSheetTransaction = new CreateOrEditCashSheetTransactionDto
            {
                CashSheetBatchID = input.CashSheetBatchID,
                CashSheetBatchAccountSummaryID = input.CashSheetBatchAccountSummaryID,
                CostpointProjectCode = input.CostpointProjectCode,
                CostpointAccountNumber = input.CostpointAccountNumber,
                AccountAbbreviation = input.AccountAbbreviation,
                CostpointOrganizationCode = input.CostpointOrganizationCode,
                TransactionDescription = input.TransactionDescription,
                TransactionAmountUSD = input.TransactionAmountUSD,
                QBAccountNumber = input.QBAccountNumber,
                QBAmount = input.QBAmount,
                QBDescription = input.QBDescription,
                QBPayee = input.QBPayee,
                QBTransactionDate = input.QBTransactionDate,
                QBTransactionNumber = input.QBTransactionNumber,
                TransactionState = input.TransactionState,
                ProblemCodes = input.ProblemCodes,
                BankAccountID = input.BankAccountID,
                CashSheetBankAccountDescription = input.CashSheetBankAccountDescription
                //Id = 0
            };
    
            //Create new transaction
            //await _cashSheetTransactionRepository.InsertAsync(cashSheetTransaction);
            await _cashSheetTransactionsAppService.CreateOrEdit(cashSheetTransaction);
    

    I receive the error:

    ?exception {"Current user did not login to the application!"} Data: {System.Collections.ListDictionaryInternal} HResult: -2146233088 HelpLink: null InnerException: null Message: "Current user did not login to the application!"

    I don't understand how the credentials could be lost along the way once inside the API method. I also started from scratch and made sure I logged on fresh to eliminate that possibility.

    Thoughts?

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @schlarmanp

    This is definitely not normal. Are you able to share your project or a reproduction project via email to [email protected] ?

  • User Avatar
    0
    marble68 created

    Was it ever resolved what the problem was here?