My minimal ASP.NET Core Web API returns error 405: Method not allowed - asp.net-core-webapi

I want to update a record by calling my api endpoint using the put verb but his returns error 405 method not allowed.
My api endpoint:
app.MapPut("/sirs/{Id}", async (int Id, SIRCContext db, Sir sirc) =>
{
var record = await db.Sirs.FindAsync(Id);
if (record is null)
return Results.NotFound();
record.ActionTaken = sirc.ActionTaken;
record.Status = sirc.Status;
record.ResolvedDate = sirc.ResolvedDate;
record.FurtherActionRequired = sirc.FurtherActionRequired;
await db.SaveChangesAsync();
return Results.NoContent();
});
My client code:
public async Task UpdateSIRC(int id, Sir sirc)
{
try
{
await SetAuthToken();
var response = await _httpClient.PutAsJsonAsync($"/sirs/{id}", sirc);//Error here
response.EnsureSuccessStatusCode();
statusMessage = "Update Successful";
}
catch (Exception)
{
statusMessage = "Failed to update data.";
}
}

I found out that adding the below to web.config works. The api was only working in debug mode and not when deployed to IIS.
<system.webServer>
<modules runAllManagedModulesForAllRequests="false">
<remove name="WebDAVModule" />
</modules>
</system.webServer>

Related

The HTTP request is unauthorized with client authentication scheme 'Anonymous' Dynamics NAV

I have an issue regarding an .net core app (3.1) trying to consume a WS (SOAP) from a Dynamics Nav Server. (on-premise).
When I'm in debugging mode everything works fine, but when I deployed the app to local IIS server I keep getting
"The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'oRswGaADCgEAoxIEEAEA=,Negotiate'."
Client initialization
private async Task<TXSiteUser_PortClient> GetInstanceAsync()
{
EndpointAddress endpointAddress = new EndpointAddress(serviceUrl);
BasicHttpBinding basicHttpBinding = new BasicHttpBinding()
{
MaxReceivedMessageSize = int.MaxValue,
MaxBufferSize = int.MaxValue,
OpenTimeout = TimeSpan.MaxValue,
CloseTimeout = TimeSpan.MaxValue,
ReceiveTimeout = TimeSpan.MaxValue,
SendTimeout = TimeSpan.MaxValue
};
return await Task.Run(() => new TXSiteUser_PortClient(basicHttpBinding, endpointAddress));
}
Example of call to a WS
var client = await GetInstanceAsync();
var user = await client.ReadAsync(new Read { Id = id });
await client.CloseAsync();
return user.TXSiteUser;
The Dynamics server has the credential type "Windows".
And below is the web.config
<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath=".\ArtoilPortal.exe" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
</system.webServer>
</location>
</configuration>
The weird thing is that when I'm in debugging mode and testing the endpoints from Swagger, everything works ok. Also, everytime I try to use postman I get a 401, no matter of Auth type (Basic,NTML).
Also i've tried adding
basicHttpBinding.Security.Mode = BasicHttpSecurityMode.Transport;
basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
var username = _appSettings.NAVCredentials.UserName;
var password = _appSettings.NAVCredentials.Password;
and extended the PortClient method like this
public TXSiteUser_PortClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress, string userName, string password) :
base(binding, remoteAddress)
{
this.ChannelFactory.Credentials.UserName.UserName = userName;
this.ChannelFactory.Credentials.UserName.Password = password;
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
with no luck
managed to solve the errors.
Firstly, the client initialization code was fixed by using "TransportCredentialOnly" instead of "Transport"
basicHttpBinding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
TransportCredentialOnly mode is used when there's no SSL configured for the SOAP services on Dynamics NAV server.
And the version of "PortClient" method that works for me looks like this:
public TXSiteUser_PortClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress, string userName, string password) :
base(binding, remoteAddress)
{
this.ClientCredentials.Windows.ClientCredential.UserName = userName;
this.ClientCredentials.Windows.ClientCredential.Password = password;
this.ClientCredentials.Windows.ClientCredential.Domain = "";
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
In the original post I was setting
this.ChannelFactory.Credentials.UserName.UserName = userName;
Where it should've been
this.ClientCredentials.Windows.ClientCredential.UserName = userName;
Hopefully this will help somebody at some point

The server committed a protocol violation. Section=ResponseStatusLine Violation error

Please, someone help me. I've been researching this for hours, have tried lots of different suggested fixes, and I'm at a total loss as to why I am still getting this error.
I have an ASP.NET MVC .NET Framwork 4.5 web page with the following code in it:
public async Task<ActionResult> LoadData()
{
string apiUrl = "http://demo.com/GetData/";
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(apiUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new
System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
var input = new { UserName = "Test", fromDate = "2020-01-01", toDate = "2020-01 01" };
var response1 = await client.PostAsJsonAsync("Product", input);
string postresponse1 = "";
if (response1.IsSuccessStatusCode)
{
var data = await response1.Content.ReadAsStringAsync();
var table = JsonConvert.DeserializeObject<Wrapper<List<PartOne>>>(data).Data;
if (table != null && table.Count > 0)
{
postresponse1 = ConvertResponseToXML(table.ToList(), input.UserName, input.fromDate, input.toDate).ToString();
var responseMSG = _dbCon.InsertResponseXMLDataToDatabase(postresponse1).ToString();
}
}
}
return View();
}
I have this in my web.config:
<system.net>
<settings>
<httpWebRequest useUnsafeHeaderParsing="true" />
</settings>
<defaultProxy>
<proxy bypassonlocal="True" usesystemdefault="False" />
</defaultProxy>
</system.net>
When I am running project on my dev machine message response return:
The server committed a protocol violation. Section=ResponseStatusLine

Receiving a magic login link on Azure without allowing double escaping

Trying to implement a magic link login on an Asp.net core 2.1 Web app. Works like a charm locally, however when deploying to Azure I get an error message: `The request contained a double escape sequence and request filtering is configured on the Web server to deny double escape sequences
MagicLinkSender.cs
var token = await _userManager.GenerateUserTokenAsync(
user: user,
tokenProvider: "MagicLinkTokenProvider",
purpose: "magic-link"
);
var magiclink = _urlHelper.Link(
routeName: "MagicLinkRoute",
values: new { userid = user.Id, token = token, });
AccountController
[HttpGet("/magic/{userid}/{token}", Name = "MagicLinkRoute")]
public async Task<IActionResult> MagicLogin([FromRoute]string userid, [FromRoute]string token )
{
// Sign the user out if they're signed in
if(_signInManager.IsSignedIn(User))
{
await _signInManager.SignOutAsync();
}
var user = await _signInManager.UserManager.FindByIdAsync(userid);
if(user != null)
{
token = token.Replace("%2F", "/");
var isValid = await _signInManager.UserManager.VerifyUserTokenAsync(
user: user,
tokenProvider: "MagicLinkTokenProvider",
purpose: "magic-link",
token: token
);
if(isValid)
{
await _signInManager.UserManager.UpdateSecurityStampAsync(user);
await _signInManager.SignInAsync(user, isPersistent: true);
}
}
return RedirectToPage("/Profile/Index");
}
Seems like I can get around this with allowing doublescaping in web.config:
<system.webServer>
<security>
<requestFiltering allowDoubleEscaping="true" />
</security>
</system.webServer>
However this seems to open some security holes. Are there better alternatives to get this working on Azure?
Some characters require additional configuration depending on your hosting environment:
To allow '+' in item names in ASP.NET 2.0 and 4.0 set the configuration\in your web.config file.
<system.webServer>
<security>
<requestFiltering allowDoubleEscaping="true" />
</security>
</system.webServer>
To allow '&' and '%' in ASP.NET 4.0 set the configurationin your web.config file.
<system.web>
<httpRuntime requestPathInvalidCharacters=""/>
</system.web>
To allow trailing dots ('.') in ASP.NET 4.0 set configuration in your web.config file.
<system.web>
<httpRuntime relaxedUrlToFileSystemMapping="true"/>
</system.web>
For more details, you could refer to this article and this one.
Changing to use parameters from querystring instead of route seems to solve this problem when deploying to Azure.
[HttpGet("/magic", Name = "MagicLinkRoute")]
public async Task<IActionResult> MagicLogin([FromQuery]string userid, [FromQuery]string token )
{
// ...

AngularJS with Web API: uploading files

Good day,
I have beed following at tutorial on how to upload files using angularjs using WebAPI from this link https://code.msdn.microsoft.com/AngularJS-with-Web-API-22f62a6e#content It has been quite helpful,
In the tutorial there are four method
Task<IEnumerable<PhotoViewModel>> Get();
Task<PhotoActionResult> Delete(string fileName);
Task<IEnumerable<PhotoViewModel>> Add(HttpRequestMessage request);
bool FileExists(string fileName);
Now i need another method to get image by the image name, i have been trying with no result
//Task<PhotoActionResult> GetById(string fileName);
Please i need help on how to get file by id working
Thanks.
I have got the solution to the question, thanks to the author of the post #JayChase
I add a new method to IPhotoManager:
Task<PhotoViewModel> Get(string fileName);
The concrete implementation would be similar to the existing get but returning a single PhotoViewModel and having the query filter by filename as well:
public async Task<PhotoViewModel> Get(string fileName)
{
PhotoViewModel photo = new PhotoViewModel();
DirectoryInfo photoFolder = new DirectoryInfo(this.workingFolder);
await Task.Factory.StartNew(() =>
{
photo = photoFolder.EnumerateFiles()
.Where(fi => new[] { ".jpg", ".bmp", ".png", ".gif", ".tiff" }.Contains(fi.Extension.ToLower())
&& fi.Name == fileName
)
.Select(fi => new PhotoViewModel
{
Name = fi.Name,
Created = fi.CreationTime,
Modified = fi.LastWriteTime,
Size = fi.Length / 1024
})
.FirstOrDefault();
});
return photo;
}
Finally add the Get(string filename) method to the photo api:
public async Task<IHttpActionResult> Get(string fileName)
{
var result = await photoManager.Get(fileName);
return Ok(new { photo = result });
}
Moreso, I have followed your sample project and also implement the getById method from the WebAPI and all work fine from you sample project, i try to replicate the project from a fresh ASP.Net Empty Web APi, after following the step in creating the project by copig the exact code to a new project, on testing the services, GetAll and POST Method work well, but the GetById and DELETE method do return 404 error but in your sample project all works well,
http://prntscr.com/6vp958
But if i point to a wrong filename i get an empty array of photo: http://prntscr.com/6vpa4q
I resolved the issues by adding
<!--add below sectio to allow DELETE method-->
<validation validateIntegratedModeConfiguration="false" />
<modules runAllManagedModulesForAllRequests="true">
<remove name="WebDAVModule" />
</modules>
to the web.config file
The authour also comment:
If you are still getting 404 or 405 errors when trying to call the new get method then you may need to add the route attribute like below:
[Route("{fileName}")]
public async Task<IHttpActionResult> Get(string fileName)
{
...
}
Thanks all for the solution to the problem, hope this will help someone
Thanks.

GooglePlus OWIN Storing multiple claims cause AuthenticationManager.GetExternalLoginInfoAsync() to return null

I have an issue in my app where AuthenticationManager.GetExternalLoginInfoAsync() returns null after the first successful call AND when it becomes authenticated as I record two claims(access token and refresh token). If I record just the access token, life seems to be good and the AuthenticationManager.GetExternalLoginInfoAsync() continues to return a non-null LoginInfo payload. If I record any other random types of claims, like "test", "foo" "wacka wacka" it also still works.
So the code where I frame up the GooglePlus options looks as follows
var googleOptions = new GooglePlusAuthenticationOptions
{
ClientId = clientId,
ClientSecret = clientSecret,
RequestOfflineAccess = true,
Provider = new GooglePlusAuthenticationProvider
{
OnAuthenticated = async context =>
{
context.Identity.AddClaim(new Claim(GooglePlusAccessTokenClaimType, context.AccessToken));
context.Identity.AddClaim(new Claim("urn:tokens:Test", "Test"));
//context.Identity.AddClaim(new Claim(GooglePlusRefreshTokenClaimType, context.RefreshToken));
}
}
};
googleOptions.Scope.Add("https://www.googleapis.com/auth/plus.me");
googleOptions.Scope.Add("https://www.google.com/m8/feeds");
googleOptions.Scope.Add("https://www.googleapis.com/auth/calendar.readonly");
googleOptions.Scope.Add("https://www.googleapis.com/auth/calendar");
This first line in the method is the one that horribly returns null
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
I've definitely gone up and down Google and StackOverflow, Microsoft's various pages and seen the suggestion to do
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
Session["dummy"] = "dummy";
ViewBag.ReturnUrl = returnUrl;
return View();
}
or add the following to the web.config
<location path="signin-googleplus">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
Unfortunately, non of it is working. If saving the refresh token is not best practices as well, I'm very opening to knowing what is.
Much appreciation in advance!

Resources