When I make a POST to my Web API I get a 401 exception - ajax

I am making an Ajax call to a Web Core API server using localhost on my development machine.
This is the code that makes the call;
addDataToDatabase = function (callback, errorCallback, url, data) {
$.ajax({
async: true,
url: url,
contentType: "application/json",
dataType: "text",
data: data,
type: "POST",
xhrFields: { withCredentials: true }
})
.done(function (data) {
callback(data);
})
.fail(function (data) {
errorCallback(data);
});
However I get a 401 unauthorised user exception.
In my startup Configure method I set up CORS as follows
var url = Configuration["originUrl"];
app.UseCors(
options => options.WithOrigins(url).AllowAnyHeader().AllowAnyMethod().AllowCredentials()
);
In my appSettings file I set the orginUrl:
"originUrl": "http://localhost:12345"
Which all works for GET type calls but not POST.
What do I need to fix?
EDIT - upon request this is the full startup code;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
namespace Properties.API
{
using Domain;
using Domain.Interfaces;
using Domain.Repo;
using EF6;
using Helper;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NLog;
using NLog.Config;
using NLog.Extensions.Logging;
using NLog.Web;
using Sir.EF6;
using Sir.EF6.Interfaces;
using Sir.EF6.Repo;
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appSettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appSettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public static IConfigurationRoot Configuration;
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new AssemblyPart(typeof(Startup).Assembly));
services.AddSingleton(manager);
services.AddCors();
services.AddMvcCore().AddJsonFormatters();
services.Configure<IISOptions>(options => new IISOptions
{
AutomaticAuthentication = true,
ForwardClientCertificate = false,
ForwardWindowsAuthentication = false
});
var connectionStringMSurveyV2 = Configuration.GetConnectionString("MSurveyV2Db");
services.AddScoped<MSurveyV2Db>(_ => new MSurveyV2Db(connectionStringMSurveyV2));
var connectionStringSir = Configuration.GetConnectionString("SirDb");
services.AddScoped<SirDb>(_ => new SirDb(connectionStringSir));
services.AddScoped<IPropertiesRepo, PropertiesRepo>();
services.AddScoped<ISirUoW, SirUoW>();
services.AddScoped<IPropertyUoW, PropertyUoW>();
services.AddScoped<Services.IMailService, Services.MailService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
loggerFactory.AddDebug();
loggerFactory.AddNLog();
LogManager.Configuration = new XmlLoggingConfiguration(#"nlog.config");
var connectionString = Configuration.GetConnectionString("SirNLogDb");
LogManager.Configuration.Variables["SirNLogDb"] = connectionString;
app.AddNLogWeb();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler();
}
var url = Configuration["originUrl"];
app.UseCors(
options => options.WithOrigins(url).AllowAnyHeader().AllowAnyMethod().AllowCredentials()
);
app.UseMvc();
AutomapperInit.Set();
var logger = LogManager.GetCurrentClassLogger();
logger.Info("Started Properties Web API");
logger.Info($"Origin url = {url}");
}
}
}
This is a POST method that provokes the 401 exception;
[HttpPost("add")]
public async Task<IActionResult> Add([FromBody] InspectionVisitInputDto inspectionVisitDto)
{
this.NLogger.Info("api/inspectionVisit/add".ToPrefix());
if (inspectionVisitDto == null)
{
NLogger.Error("No data sent");
return BadRequest(ModelState);
}
if (inspectionVisitDto.InspectionId < 1)
{
NLogger.Error($"Invalid InspectionId < 1 (actual value is {inspectionVisitDto.InspectionId}");
return BadRequest(ModelState);
}
var inspectionVisit = Mapper.Map<InspectionVisit>(inspectionVisitDto);
var dateOfVisit = inspectionVisit.DateOfVisit.Date;
try
{
var existingInspectionVisit = this.SirUoW.InspectionVisit.GetItem(
x => x.InspectionId == inspectionVisit.InspectionId &&
DbFunctions.TruncateTime(x.DateOfVisit) == dateOfVisit &&
x.ContractSubcontractorId == inspectionVisit.ContractSubcontractorId &&
x.SelectedInspectorUsername == inspectionVisit.SelectedInspectorUsername &&
x.WorkPhaseId == inspectionVisit.WorkPhaseId);
if (existingInspectionVisit?.InspectionVisitId > 0)
{
NLogger.Info($"Inspection Visit Id = {existingInspectionVisit.InspectionVisitId} already exists.");
return Ok(existingInspectionVisit.InspectionVisitId);
}
}
catch (Exception e)
{
var message = "Cannot get inspection visit";
await ReportException(e, message);
var status = OperationStatus.CreateFromException(message, e);
return BadRequest(status);
}
NLogger.Info("Add New Inspection Visit");
try
{
this.SirUoW.InspectionVisit.Add(inspectionVisit);
await SirUoW.LoggedInSaveChangesAsync(this.LoggedInUser);
NLogger.Info("New Inspection Visit Saved, id = " + inspectionVisit.InspectionVisitId);
if (inspectionVisit.ContractSubcontractorId != null)
{
await SaveContractSubcontractorIdForInspection(inspectionVisitDto);
}
return Ok(inspectionVisit.InspectionVisitId);
}
catch (Exception e)
{
var message = "Cannot save " + inspectionVisitDto;
await ReportException(e, message);
var status = OperationStatus.CreateFromException(message, e);
return BadRequest(status);
}
}
And this is a GET method that works OK
[HttpGet("getall")]
public async Task<IActionResult> GetContracts()
{
this.NLogger.Info("api/contracts/getall".ToPrefix());
try
{
var contracts = this.SirUoW.ViewAllContracts.GetList(x => x.IsMainDivision && x.EndDate == null).OrderBy(x => x.FullContractNo).ToList();
return Ok(contracts);
}
catch (Exception e)
{
var message = "Error getting contracts";
await ReportException(e, message);
var status = OperationStatus.CreateFromException(message, e);
return BadRequest(status);
}
}

Your scenario is combining IIS Windows Authenticaiton and CORS, since preflight request OPTIONS would not contains any security header, it will hit the error 401.
Try workaround below:
Enable anonymousAuthentication in appsettings.json
{
"iisSettings": {
"windowsAuthentication": true,
"anonymousAuthentication": true
}
}
For enable authentication, try Filter to disable anonymous access.
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});

Related

openiddict with Owin - Unable to obtain configuration from .well-known/openid-configuration

I've been trying an OpenID authorization code flow using openiddict. Both the OIDC client and server are implemented using OpenIddict (with Owin) and both are web forms apps hosted in IIS.
The client side config:
public bool SetUp(IAppBuilder app)
{
try
{
var container = CreateContainer();
app.UseAutofacLifetimeScopeInjector(container);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieHttpOnly = true,
CookieSecure = CookieSecureOption.SameAsRequest
});
app.UseMiddlewareFromContainer<OpenIddictClientOwinMiddleware>();
DependencyResolver.SetResolver(new
AutofacDependencyResolver(container));
var scope = container.BeginLifetimeScope();
return true;
}
catch (Exception ex)
{
_logger.Error("OidcController: SetUp", ex);
return false;
}
}
private IContainer CreateContainer()
{
IdentityModelEventSource.ShowPII = true;
var services = new ServiceCollection();
services
.AddOpenIddict()
.AddCore(options => { })
.AddClient(options => {
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate()
.UseOwin()
.EnableRedirectionEndpointPassthrough();
options.UseSystemNetHttp();
//Try to reuse key cloak options
options.AddRegistration(new OpenIddictClientRegistration
{
Issuer = new Uri(_oidcSettings.ExternalUrl.RemoveAuth(), UriKind.Absolute),
ClientId = _oidcSettings.ClientId,
ClientSecret = _oidcSettings.ClientSecret,
RedirectUri = <the redirect uri>,
Scopes = { "email", "profile", "offline_access" }
});
});
var builder = new ContainerBuilder();
builder.Populate(services);
builder.RegisterControllers(typeof(OidcController).Assembly);
return builder.Build();
}
The server config:
protected void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
var services = new ServiceCollection();
services.AddOpenIddict()
// Register the OpenIddict core components.
.AddCore(options =>
{
options.UseEntityFramework()
.UseDbContext<ApplicationDbContext>();
})
// Register the OpenIddict server components.
.AddServer(options =>
{
// Enable the authorization and token endpoints.
options.SetAuthorizationEndpointUris("/connect/authorize")
.SetTokenEndpointUris("/connect/token");
options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles);
options.AllowAuthorizationCodeFlow();
// Register the signing and encryption credentials.
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
options.UseOwin()
.EnableAuthorizationEndpointPassthrough();
})
// Register the OpenIddict validation components.
.AddValidation(options =>
{
options.UseLocalServer();
// Register the OWIN host.
options.UseOwin();
});
// Create a new Autofac container and import the OpenIddict services.
var builder = new ContainerBuilder();
builder.Populate(services);
Provider = new ContainerProvider(builder.Build());
Task.Run(async delegate
{
using var scope = Provider.ApplicationContainer.BeginLifetimeScope();
var context = scope.Resolve<ApplicationDbContext>();
context.Database.CreateIfNotExists();
var manager = scope.Resolve<IOpenIddictApplicationManager>();
//Add the client app
if (await manager.FindByClientIdAsync("SAS-Console") == null)
{
await manager.CreateAsync(new OpenIddictApplicationDescriptor
{
ClientId = "SAS-Console",
ClientSecret = "413ca0fd-8e24-4362-aaa3-d74095227658",
ConsentType = ConsentTypes.Implicit,
DisplayName = "Console application",
RedirectUris =
{
<client app redirect url>
},
Permissions =
{
Permissions.Endpoints.Authorization,
Permissions.Endpoints.Token,
Permissions.GrantTypes.AuthorizationCode,
Permissions.ResponseTypes.Code
}
});
}
}).GetAwaiter().GetResult();
}
But when I run both, I continue to get an error:
IDX20803: Unable to obtain configuration from: 'https:///.well-known/openid-configuration

google calendar api net api The access token has expired and could not be refreshed. Errors: refresh error, refresh error, refresh error

I'm new to google api. I'm using google .net api, I'm getting the error:
The access token has expired and could not be refreshed. Errors: refresh error, refresh error, refresh error
I'm able to see the login screen, I accept the permissions request, and I got redirected to Index 2 correctly. When the calendar service is going to be used (execute) it crashes with the already mentioned error.
I already set the token as offline in the GOOGLE_AUTH_URL_TEMPLATE variable. That is supposed to be used for long term tokens.
Main Auth Class
public class TestOAuth2
{
private string applicationName = "g calendar test api";
private string clientId = "---";//From Google Developer console https://console.developers.google.com
private string clientSecret = "---";//From Google Developer console https://console.developers.google.com
private string[] scopes = new string[] {
CalendarService.Scope.Calendar, // Manage your calendars
CalendarService.Scope.CalendarReadonly // View your Calendars
};
private UserCredential userCredential;
string GOOGLE_AUTH_URL_TEMPLATE = "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&approval_prompt=force&scope={0}&response_type=code&redirect_uri={1}&client_id={2}&login_hint=";
string clientRedirectUri= "http://localhost:57618/Home/Index2";
private CalendarService calendarService;
public TestOAuth2(string access_token)
{
ClientSecrets clientSecrets = new ClientSecrets
{
ClientId = clientId,
ClientSecret = clientSecret
};
GoogleAuthorizationCodeFlow flow = null;
TokenResponse token = new TokenResponse
{
AccessToken = access_token
};
flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = clientSecrets,
Scopes = scopes
});
userCredential = new UserCredential(flow, "user", token);
calendarService = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = userCredential,
ApplicationName = applicationName
});
}
public string GetGoogleOAuthURL()
{
return string.Format(GOOGLE_AUTH_URL_TEMPLATE, String.Join("+", scopes), clientRedirectUri, clientId);
}
private void InitializeCalendarService()
{
calendarService = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = userCredential,
ApplicationName = applicationName
});
}
public string GetEvents()
{
try
{
if (calendarService == null)
{
InitializeCalendarService();
}
var eventRequest = calendarService.Events.List("primary");
eventRequest.TimeMin = DateTime.Now;
eventRequest.ShowDeleted = false;
eventRequest.SingleEvents = true;
eventRequest.MaxResults = 10;
eventRequest.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime;
var events = eventRequest.Execute();
string sEvents = "";
foreach (var evt in events.Items)
{
sEvents += evt.Description + "\n<br>";
}
return sEvents;
}
catch (Exception ex)
{
return ex.Message;
}
}
}
Home Controller
public class HomeController : Controller
{
private TestOAuth2 Google;
public string GetGoogleOauthUrl()
{
string temp = "";
try
{
Google = new TestOAuth2(string.Empty);
temp = Google.GetGoogleOAuthURL();
}
catch (Exception ex)
{
temp = ex.Message;
}
return temp;
}
public string GetEvents()
{
string access_token = Request.Headers.Get("access_token");
string temp = "";
try
{
Google = new TestOAuth2(access_token);
temp = Google.GetEvents();
}
catch (Exception ex)
{
temp = ex.Message;
}
return temp;
}
public ActionResult Index()
{
return View();
}
public ActionResult Index2()
{
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
Index View
<html>
<body>
<script>
document.addEventListener('DOMContentLoaded', function (event) {
fetch("http://localhost:57618/Home/GetGoogleOauthUrl", {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => {
if (response.ok) {
response.text().then(myAnswer => {
console.log(myAnswer);
window.open(myAnswer, '_blank');
})
} else {
console.error.log("HTTP-Error: " + response.status);
}
});
});
</script>
Hello! Wait For A New Window!
</body>
</html>
Index 2 View
<html>
<body>
<script>
function getUrlParameter(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
var results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
};
document.addEventListener('DOMContentLoaded', function (event) {
let token = getUrlParameter("code");
fetch("http://localhost:57618/Home/GetEvents", {
method: 'GET',
headers: {
'Content-Type': 'application/json',
"access_token": token
}
})
.then(response => {
if (response.ok) {
response.text().then(myAnswer => {
console.log(myAnswer);
document.write("<br><br><br>");
document.write(myAnswer);
})
} else {
console.error.log("HTTP-Error: " + response.status);
}
});
});
</script>
</body>
</html>
What you want to use is a special token that does not need to be refreshed I believe.
To do that you need to create a project inside GCP and then create a Service Account and Download a json key inside the new Service Account created.
Enable the Calendar API inside the new created project and copy the email of the service account. Go to the calendar you want to give access to your api and add the email you copy from the service account. Now you can use the service account json key to authenticate and it wont have expiration.

IdentityServer4 Access token updating

Last week I am trying to configure the IdentityServer4 to get an access token automatically updating.
I had an API:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5100";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
});
My MVC client configuration:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5100";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("api1");
options.Scope.Add("offline_access");
});
And the IdentityServer's clients configuration:
return new List<Client>
{
new Client
{
ClientId = "mvc",
ClientName = "My mvc",
AllowedGrantTypes = GrantTypes.Hybrid,
RequireConsent = false,
AccessTokenLifetime = 10,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:5102/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5102/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"api1"
},
AllowOfflineAccess = true
}
};
On the client side I use AJAX queries to call the API to get/post/put/delete data. I add the access token to the request and get the result.
private async getAuthenticationHeader(): Promise<any> {
return axios.get('/token').then((response: any) => {
return { headers: { Authorization: `Bearer ${response.data}` } };
});
}
async getAsync<T>(url: string): Promise<T> {
return this.httpClient
.get(url, await this.getAuthenticationHeader())
.then((response: any) => response.data as T)
.catch((err: Error) => {
console.error(err);
throw err;
});
}
The access token is provided by the MVC client method:
[HttpGet("token")]
public async Task<string> GetAccessTokenAsync()
{
return await HttpContext.GetTokenAsync("access_token");
}
It works fine. After access token expired I get 401 on the client side, so it would be great to have an opportunity to update access token automatically when it was expired.
According to a documentation I supposed, that It can be reached by setting AllowOfflineAccess to true and adding suitable scope "offline_access".
Maybe I don't understand the right flow of the access and refresh tokens usages. Can I do it automatically or it is impossible? I suppose, that we can use refresh tokens in out queries, but I don't understand how.
I've read a lot of SO answers and github issues but I am still confused. Could you help me to figure out?
After investigation and communicating in comments I've found the answer. Before every API call I get the expite time and according to the result update access_token or return existing:
[HttpGet("config/accesstoken")]
public async Task<string> GetOrUpdateAccessTokenAsync()
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var expiredDate = DateTime.Parse(await HttpContext.GetTokenAsync("expires_at"), null, DateTimeStyles.RoundtripKind);
if (!((expiredDate - DateTime.Now).TotalMinutes < 1))
{
return accessToken;
}
lock (LockObject)
{
if (_expiredAt.HasValue && !((_expiredAt.Value - DateTime.Now).TotalMinutes < 1))
{
return accessToken;
}
var response = DiscoveryClient.GetAsync(_identitySettings.Authority).Result;
if (response.IsError)
{
throw new Exception(response.Error);
}
var tokenClient = new TokenClient(response.TokenEndpoint, _identitySettings.Id, _identitySettings.Secret);
var refreshToken = HttpContext.GetTokenAsync("refresh_token").Result;
var tokenResult = tokenClient.RequestRefreshTokenAsync(refreshToken).Result;
if (tokenResult.IsError)
{
throw new Exception();
}
accessToken = tokenResult.AccessToken;
var idToken = HttpContext.GetTokenAsync("id_token").Result;
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.IdToken,
Value = idToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = accessToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.RefreshToken,
Value = tokenResult.RefreshToken
}
};
var expiredAt = DateTime.UtcNow.AddSeconds(tokenResult.ExpiresIn);
tokens.Add(new AuthenticationToken
{
Name = "expires_at",
Value = expiredAt.ToString("o", CultureInfo.InvariantCulture)
});
var info = HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme).Result;
info.Properties.StoreTokens(tokens);
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, info.Principal, info.Properties).Wait();
_expiredAt = expiredAt.ToLocalTime();
}
return accessToken;
}
}
I call this method to get the access_token and add int to the API call headers:
private async getAuthenticationHeader(): Promise<any> {
return axios.get('config/accesstoken').then((response: any) => {
return { headers: { Authorization: `Bearer ${response.data}` } };
});
}
async getAsync<T>(url: string): Promise<T> {
return this.axios
.get(url, await this.getAuthenticationHeader())
.then((response: any) => response.data as T)
.catch((err: Error) => {
console.error(err);
throw err;
});
}
Double check locking were implemented to prevent simultamious async API calls try to change access_token at the same time. Optionally you can cashe you access_token into static variable or cache, it is up to you.
If you have any advices or alternatives it would be insteresting to discuss. Hope it helps someone.
There's 2 ways of doing this:
Client side - Handle the authentication and obtaining of the token on the client side using a lib like oidc-client-js. This has a feature that allows automatic renewal of the token via a prompt=none call to the authorize endpoint behind the scenes.
Refresh token - store this in your existing cookie and then use it to request a new access token as needed. In this mode your client side code doing the AJAX calls would need to be aware of token errors and automatically request a new token from the server whereby GetAccessTokenAsync() could use the refresh token to get a new access token.

Angular2 to Asp.net Core Webapi IFormFile is null

I am using angular2 with asp.net core webapi. Using the following code to send file information. IFormFile is always null. I have used same name in post input and in api method parameter, still no luck. Please help me.
FormData
this.formData.append("file", f,f.name);
Component method
public UploadFiles() {
console.log("Form Data:" + this.formData);
let saved: boolean = false;
this.claimsService
.UploadFiles(this.formData)
.subscribe(data => {
saved = data;
}, error => {
console.log(error)
swal(error);
})
}
Service Method
UploadFiles(data: FormData): Observable<boolean> {
return this.ExecuteFilePost("Upload/Upload", data);
}
Base Service Method:
public ExecuteFilePost(action: string, data: FormData) {
let _body = data;
let headers = new Headers({ 'Content-Type': undefined});
let url = this._baseUrl + action;
let requestoptions: RequestOptions = new RequestOptions({
headers: headers,
});
console.log('req url:' + url);
return this.http.post(url,_body,requestoptions)
.share()
.map((res: Response) => {
if (res.status < 200 || res.status >= 300) {
throw new Error('This request has failed ' + res.status);
}
else {
return res.json();
}
});
}
WebApi Method
[HttpPost]
[ActionName("Upload")]
public async Task Upload(IFormFile file)
{
var uploads = Path.Combine(_environment.WebRootPath, "uploads");
// foreach (var file in files)
// {
if (file.Length > 0)
{
using (var fileStream = new FileStream(Path.Combine(uploads, file.FileName), FileMode.Create))
{
await file.CopyToAsync(fileStream);
}
}
// }
}
Chrome request screens
enter image description here
I know the post is old, but maybe for others. You can do things like this:
HTML:
<input #fileInput type="file" multiple />
<button (click)="addFile()">Add</button>
Component:
#ViewChild("fileInput") fileInput;
addFile(): void {
let fi = this.fileInput.nativeElement;
if (fi.files) {
let fileToUpload = fi.files;
this.settingsService
.upload(fileToUpload)
.subscribe(res => {
console.log(res);
});
}
}
Service:
upload(files: any) {
let formData = new FormData();
for (let i = 0; i < files.length; i++) {
formData.append("files", files[i]);
}
return this.http
.post(fileUploadApi, formData)
.map(response => this.extractData(response as Response))
.catch(error => this.httpErrorHandlerService.responseError(error));
}
Web API
[HttpPost]
[Route("Upload")]
public IActionResult UploadFiless([FromForm] IFormFileCollection files)
{
try
{
return this.Ok();
}
catch (Exception)
{
return this.BadRequest();
}
}

XmlHttpRequest.responseJSON is not returned on ajaxError function

I'm trying to handle all ajax exceptions globally in my application.
I have a simple validation exception that is thrown after an ajax post request is sent to the server.
if (string.IsNullOrEmpty(name)) { throw new Exception("Name cannot be empty."); }
I have a class the overrides the OnException method:
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
{
return;
}
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
filterContext.Result = new JsonResult
{
Data = new { message = filterContext.Exception.Message },
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
}
else
{
base.OnException(filterContext);
}
}
And a javascript function that listens to all ajax errors:
$(document).ajaxError(function(e, xhr, settings, exception) {
e.stopPropagation();
if (xhr.responseJSON != null) {
showMessage(xhr.responseJSON.message, ERROR);
}
});
The problem is, on my Azure server which is configured for https, the xhr.responseJSON is not returned. However, it works fine locally and I am able to display the exception message thrown. Is https somehow blocking the request? I have tried locally to run my application through https but I'm not able to recreate the issue.
I'm intending to use the same methodology for much more than just validation exceptions, as I know they can be easily handled from the client.
I had the same issue.
Adding filterContext.HttpContext.Response.TrySkipIisCustomErrors = true fixed the issue for me.
So in your case:
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
{
return;
}
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
filterContext.Result = new JsonResult
{
Data = new { message = filterContext.Exception.Message },
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
// Added this line
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true
}
else
{
base.OnException(filterContext);
}
}

Resources