I'm running into an issue where Angular is unable to make a successful request on 2 of 3 endpoints but I have confirmed all work with postman and that the proxy is successfully routing the calls. Chrome - Network tab shows "blocked:other" and output of "ng serve" doesn't indicate anything in regards to proxy. Is there an issue with my angular service and/or server side controller?
Controller
[Route("api/[controller]")]
[ApiController]
public class AffiliationController : ControllerBase
{
public AffiliationController(
IQueryHandler<GetAffiliationsQuery, IQueryable<Affiliation>> getAffiliationsQuery,
ICommandHandler<ToggleAffiliationExclusionCommand> toggleAffiliationExclusionsCommand)
{
_getAffiliationsQuery = getAffiliationsQuery ?? throw new ArgumentNullException(nameof(getAffiliationsQuery));
_toggleAffiliationExclusionsCommand = toggleAffiliationExclusionsCommand ?? throw new ArgumentNullException(nameof(toggleAffiliationExclusionsCommand));
}
private readonly IQueryHandler<GetAffiliationsQuery, IQueryable<Affiliation>> _getAffiliationsQuery;
private readonly ICommandHandler<ToggleAffiliationExclusionCommand> _toggleAffiliationExclusionsCommand;
// Successfully Called
[HttpGet]
public async Task<ActionResult<PaginationResult<Affiliation>>> GetAffiliations([FromQuery] PaginationModel model)
{
var affiliations = await _getAffiliationsQuery.Handle(new GetAffiliationsQuery())
.PaginateAsync(model.PageIndex,
model.PageSize);
return affiliations;
}
// Not reached via Angular
[HttpGet("{id}")]
public async Task<ActionResult<Affiliation>> Foo(int id)
{
var target = await _getAffiliationsQuery.Handle(new GetAffiliationsQuery())
.SingleOrDefaultAsync(afn => afn.Id == id);
if (target == null)
{
return NotFound();
}
await _toggleAffiliationExclusionsCommand.HandleAsync(new ToggleAffiliationExclusionCommand(id));
return target;
}
}
Angular Service
export class AffiliationService {
private readonly apiUrl = 'api/affiliation';
constructor(
private http: HttpClient
) { }
public get(model: PaginationQuery): Observable<PaginationResult<Affiliation>> {
// Works just fine
return this.http
.get<PaginationResult<Affiliation>>(`${this.apiUrl}`, { params: UtilsService.buildQueryParams(model) })
.pipe(
catchError(this.handleError)
);
}
public toggle(affiliation: Affiliation) {
// Does not work unless I pass the 'id' as a query string parameter instead of a route parameter
return this.http
.get(`${this.apiUrl}/${affiliation.id}`)
.pipe(
catchError(this.handleError)
);
}
private handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
console.error('An error occurred:', error.error.message);
} else {
console.error(`Backend returned code ${error.status}, body was:`);
console.error(error.error);
}
return throwError('Something bad happened; please try again later.');
}
}
proxy.conf.json
{
"/api/*": {
"target": "http://localhost:5000",
"secure": false,
"logLevel": "debug"
}
}
NG Serve Output
Note
Changing the 'toggle' function to the following is able to successfully reach the endpoint. But I don't understand why this works over the other way as I can call the endpoint using the route param approach via Postman
public toggle(affiliation: Affiliation) {
return this.http
.get(`${this.apiUrl}`, { params: UtilsService.buildQueryParams({ id: affiliation.id }) })
.pipe(
catchError(this.handleError)
);
}
This looks like a CORS issue.
If you want to fix this without using a plugin, you can use the WebApi Cors package.
If using a C# ASP.NET CORE project:
Refer to the following page to enable CORS and the request can be
called.
https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-2.1
If using a C# WebAPI project:
Run the following command to install a CORS package for WebApi:
Install-Package Microsoft.AspNet.WebApi.Cors
Then you can use it as follows:
using System.Web.Http;
namespace WebService
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// New code
config.EnableCors();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
and refer to the following URL for the full microsoft guide:
https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/enabling-cross-origin-requests-in-web-api
Related
I Have asp.net web api project tageting 4.5.2
and an angular 10 web app
i'm trying to make a get call from angular to my api
conversion.service.ts
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class ConversionService {
constructor(private http: HttpClient) { }
getLayers () : Observable<string[]>
{
let uri = "http://localhost:44324/api/snapshots/getLayersFromShape";
return this.http.get<string[]>(uri);
}
}
mainpage.component.ts
import { Component, OnInit } from '#angular/core';
import { ConversionService } from '../service/conversion.service';
#Component({
selector: 'app-mainpage',
templateUrl: './mainpage.component.html',
styleUrls: ['./mainpage.component.css']
})
export class MainpageComponent implements OnInit {
filePath = 'C:\\Users\\MahammadM\\Downloads\\VS Shape files\\AREA2_OBSTACLES - EDITED';
layers: string[];
constructor(private conversionService : ConversionService) {
}
getLayers() {
this.conversionService.getLayers()
.subscribe(data => this.layers = data);
}
ngOnInit(): void {
}
}
mainpage.component.html
<button (click)="getLayers()">Click me!</button>
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var cors = new EnableCorsAttribute("https://localhost:4200", "*", "*");
config.EnableCors(cors);
var jsonFormatter = config.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.Remove(config.Formatters.XmlFormatter);
jsonFormatter.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
}
}
SnapshotsController.cs
[RoutePrefix("api/snapshots")]
public class SnapshotsController : ApiController
{
private readonly IVerticalStructureValidationService _validationService;
private readonly IConversionService _conversionService;
private readonly IShapeFile _shapeService;
public SnapshotsController(IConversionService conversionService, IVerticalStructureValidationService validationService, IShapeFile shapeService)
{
_validationService = validationService;
_conversionService = conversionService;
_shapeService = shapeService;
}
[HttpGet]
[Route("getLayersFromShape")]
public IHttpActionResult GetLayersFromShape(string filePath)
{
if (string.IsNullOrEmpty(filePath))
return Ok(new string[] { "no path provided" });
var layersAndShapePath = _shapeService.GetFileLayers(filePath);
return Ok(layersAndShapePath);
}
}
in this "getLayersFromShape" method I Have break point, but when i click my button on ui, nothing happens. console is empty.
what could be the issue?
i'm new to angular, any help is highly appreciated. thanks
I have Web API Project , it is referencing dll of database entities , it contain API Controllers , that return complex objects
when i test the Web API using visual studio (browser) or Telerik Fiddler before hosting the API on windows service , it is working fine and return a json of complex objects as expected .
After i create another project for windows service to self host the wep API using Owin
when i test any controller that return a complex object , it always return null while if i test any controller that return simple string , it is working fine , please help
I don't know why same things don't work after host on windows service
here my windows service startup.cs file
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
// Configure Web API for self-host.
HttpConfiguration config = new HttpConfiguration();
var assembly = System.Reflection.Assembly.GetExecutingAssembly().Location;
string path = assembly.Substring(0, assembly.LastIndexOf("\\")) + "\\API.dll";
config.Services.Replace(typeof(IAssembliesResolver), new SelfHostAssemblyResolver(path));
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
appBuilder.UseWebApi(config);
}
}
here is windows service class which inherit from service base class
public partial class selfHosting : ServiceBase
{
public string baseAddress = "http://localhost:9000/";
private IDisposable _server = null;
public selfHosting()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
_server = WebApp.Start<Startup>(url: baseAddress);
}
protected override void OnStop()
{
if (_server != null)
{
_server.Dispose();
}
base.OnStop();
}
}
below is WebApiConfig File under another project (API)
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Formatters.Remove(config.Formatters.XmlFormatter);
config.Formatters.JsonFormatter.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
}
}
after i check localhost:9000/api/employee
it return this line
This XML file does not appear to have any style information associated with it. The document tree is shown below.
<ArrayOfEmployees xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Application.Model" i:nil="true"/>
Controller in web API Project
public class employeeController : ApiController
{
static readonly IRepository<employee> repository = new employeeRepository();
// GET api/<controller>
public IEnumerable<employee> Get()
{
return repository.GetAll();
}
// GET api/<controller>/5
public string Get(int id)
{
return "value";
}
// POST api/<controller>
public void Post([FromBody]string value)
{
}
// PUT api/<controller>/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/<controller>/5
public void Delete(int id)
{
}
}
if the get() method return simple string , it is working fine
for example
// GET api/<controller>
public string Get()
{
return "Hello" ;
}
I have created a controller assembly containing all my controllers in WebApi 2.0 and followed this article - https://www.strathweb.com/2013/08/customizing-controller-discovery-in-asp-net-web-api/ and created a CustomAssemblyResolver and added code to replace the assembly resolver in Application_Start. Here's what my code looks like:
My CustomAssemblyResolver:
public class CustomAssemblyResolver : DefaultAssembliesResolver
{
public override ICollection<Assembly> GetAssemblies()
{
ICollection<Assembly> baseAssemblies = base.GetAssemblies();
List<Assembly> assemblies = new List<Assembly>(baseAssemblies);
string thirdPartySource = "C:\\Research\\ExCoWebApi\\ExternalControllers";
if (!string.IsNullOrWhiteSpace(thirdPartySource))
{
if (Directory.Exists(thirdPartySource))
{
foreach (var file in Directory.GetFiles(thirdPartySource, "*.*", SearchOption.AllDirectories))
{
if (Path.GetExtension(file) == ".dll")
{
var externalAssembly = Assembly.LoadFrom(file);
baseAssemblies.Add(externalAssembly);
}
}
}
}
return baseAssemblies;
}
}
My Application_Start:
protected void Application_Start()
{
Debugger.Launch();
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
GlobalConfiguration.Configuration.Services.Replace(typeof(IAssembliesResolver), new CustomAssemblyResolver());
}
As you can see I'm replacing the default assembly resolver with a CustomAssemblyResolver.
Here's how I'm registering route to the third party controller:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapHttpRoute(
name: "Test",
routeTemplate: "api/thirdparty/math",
defaults: new { controller = "Math" }
);
}
This is how my MathController looks like that lives in a separate assembly:
public class MathController : ApiController
{
[HttpPost]
public int AddValues(MathRequest request)
{
int response = MathOperations.Add(request.Value1, request.Value2);
return response;
}
}
This is the endpoint I hit via postman: http://localhost/ExCoWebApi/api/thirdparty/math with a POST operation and MathRequest JSON string in the Body and this is what I get as a response:
{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost/ExCoWebApi/api/thirdparty/math'.",
"MessageDetail": "No type was found that matches the controller named 'Math'."
}
After debugging I found that the AssemblyResolver does get replaced with CustomAssemblyResolver at the Application Start but the problem is the GetAssemblies() method of CustomAssemblyResolver doesn't get called. Hence the assembly containing MathController doesn't get loaded.
What am I missing here??
EDIT
In a desperate effort to finding solution to this I stood up a test method in which I build my own HttpConfiguration object and replace the AssemblyResolver in it and it works like a charm! The GetAssemblies() function from the CustomAssemblyResolver gets called and I'm able to call my external controller.. I wonder if there's a difference in the HttpConfiguration object that is returned from "GlobalConfiguration.Configuration" vs. instantiating one manually.. Any help on this would be greatly appreciated. Here's the code for my test method:
[TestMethod]
public void TestExternalControllerCall()
{
try
{
HttpClient client = GetClient();
MathRequest mathReq = new MathRequest { Value1 = 10, Value2 = 20 };
var response = client.PostAsJsonAsync(string.Concat("http://localhost/api/thirdparty/math"), mathReq).Result;
var entResponse = response.Content.ReadAsAsync<int>().Result;
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
private HttpClient GetClient()
{
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "Test",
routeTemplate: "api/thirdparty/math",
defaults: new { controller = "Math" }
);
config.Services.Replace(typeof(IAssembliesResolver), new CustomAssemblyResolver());
HttpServer server = new HttpServer(config);
HttpClient client = new HttpClient(server);
return client;
}
Found the issue:
The reason why the GetAssemblies() was not getting called is because the callback method (WebApiConfig.Register) set for GlobalConfiguration.Configure which was calling "config.MapHttpAttributeRoutes();". The
GlobalConfiguration.Configuration.Services.Replace(typeof(IAssembliesResolver), new CustomAssemblyResolver());
code actually belongs in the callback method configured in GlobalConfiguration.Configure as opposed to Global.asax if you have a callback set for GlobalConfiguration.Configure. So I moved the IAssemblyResolver replace statement to the Register method in WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Services.Replace(typeof(IAssembliesResolver), new CustomAssemblyResolver());
// Web API routes
config.MapHttpAttributeRoutes();
}
}
I'm very confused. I'm using ASP.NET WebApi 2 as rest api and AngularJS for the SPA. My application uses 4 rest requests. but only one doesn't work and I don't know why.
I've defined the process as follows:
Client-side:
//Controller
CrudService.getRepo(selFrom, selTo).$promise.then(
function (response) {
...
},
function (err) {
$log.error('Mth: ', err);
});
//CrudService
function getRepo(selFrom, selTo) {
return ResService.ds022.query(
{
from: selFrom,
to: selTo
}
);
}
//ResService:
function ResService($resource, baseUrl) {
return {
ds022: $resource(baseUrl + '/api/qr_ds022/mth_test', {
from: '#from',
to: '#to'
}, {})
}
}
And on the other hand: Server-side (WebAPI)
[RoutePrefix("api/qr_ds022")]
public class QR_DS022Controller : ApiController
{
private TestContext db = new TestContext();
[HttpGet]
[Route("mth_test")]
public IQueryable<getRep_Result> getRepos(DateTime from, DateTime to)
{
var results = db.getRep(from, to).AsQueryable();
return results;
}
}
The Database model was created with Entity Framework 6. I have no idea where the problem is. As you can see, the route was defined correctly.
Map route one place with function parameter, try this way
public class QR_DS022Controller : ApiController
{
private TestContext db = new TestContext();
[HttpGet]
[Route("api/qr_ds022/mth_test/{from}/{to}")]
public IQueryable<getRep_Result> getRepos(DateTime from, DateTime to)
{
var results = db.getRep(from, to).AsQueryable();
return results;
}
}
According to this question I cannot immplement IHttpModule when self hsoting a web api on a windows service
Is there a way to add an httpModule when webApi is running with the HttpSelfHostServer?
However, I still need to add this code somewhere in my self hosted web api.
I found this blog about how to fix that:
http://www.silver-it.com/node/182
The code is as follows, however I can not have an IhttpModule implemented on aself hosted API
public class CORSPreflightModule : IHttpModule
{
private const string OPTIONSMETHOD = "OPTIONS";
private const string ORIGINHEADER = "ORIGIN";
private const string ALLOWEDORIGIN = "https://yourspodomain.sharepoint.com";
void IHttpModule.Dispose()
{
}
void IHttpModule.Init(HttpApplication context)
{
context.PreSendRequestHeaders += (sender, e) =>
{
var response = context.Response;
if (context.Request.Headers[ORIGINHEADER] == ALLOWEDORIGIN)
{
response.Headers.Add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
response.Headers.Add("Access-Control-Allow-Headers", "Content-Type");
}
if (context.Request.HttpMethod.ToUpperInvariant() == OPTIONSMETHOD && context.Request.Headers[ORIGINHEADER] == ALLOWEDORIGIN)
{
response.Headers.Clear();
response.Headers.Add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
response.Headers.Add("Access-Control-Allow-Origin", "https://yourspodomain.sharepoint.com");
response.Headers.Add("Access-Control-Allow-Credentials", "true");
response.Headers.Add("Access-Control-Allow-Headers", "Content-Type");
response.Clear();
response.StatusCode = (int)HttpStatusCode.OK;
}
};
}
}
My self hosted web api is like this:
Program.cs
static void Main()
{
try
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new APIServiceTest()
};
ServiceBase.Run(ServicesToRun);
}
catch (Exception ex)
{
throw ex;
}
}
class Startup
{
// Hack from https://stackoverflow.com/a/17227764/19020 to load controllers in
// another assembly. Another way to do this is to create a custom assembly resolver
//Type valuesControllerType = typeof(OWINTest.API.ValuesController);
// This code configures Web API. The Startup class is specified as a type
// parameter in the WebApp.Start method.
public void Configuration(IAppBuilder appBuilder)
{
try
{
//Debugger.Launch();
// Configure Web API for self-host.
HttpConfiguration config = new HttpConfiguration();
config.MessageHandlers.Add(new CustomHeaderHandler());
var corsAttr = new EnableCorsAttribute(System.Configuration.ConfigurationManager.AppSettings["DominioSharePoint"].ToString(), "*", "*");
config.EnableCors(corsAttr);
// Enable attribute based routing
// http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
appBuilder.UseWebApi(config);
}
catch (Exception ex)
{
throw ex;
}
}
}
My controller:
[EnableCors(origins: "https://xx.sharepoint.com", headers: "*", methods: "*")]
public class CuentasCobroController : ApiController
{
However because its self hosted I cant implement an IHttpModule there as explained above, but I can create a custom handler how can I implemente the code above from the blog in the custom handler?
public class CustomHeaderHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken)
.ContinueWith((task) =>
{
HttpResponseMessage response = task.Result;
response.Headers.Add("Access-Control-Allow-Origin", "*");
return response;
});
}
}
Question is, how can I integrate first code, into the startup of my windows service?
You are almost there by using DelegatingHandler instead of IHttpModule.
config.MessageHandlers.Add(new CorsHeaderHandler());
DelegatingHandler.SendAsync can access both request and response.
public class CorsHeaderHandler : DelegatingHandler
{
private const string OPTIONSMETHOD = "OPTIONS";
private const string ORIGINHEADER = "ORIGIN";
private const string ALLOWEDORIGIN = "https://yourspodomain.sharepoint.com";
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var allowedOrigin = request.Headers.Any(t => t.Key == ORIGINHEADER && t.Value.Contains(ALLOWEDORIGIN));
if (allowedOrigin == false) return task.Result;
if (request.Method == HttpMethod.Options)
{
var emptyResponse = new HttpResponseMessage(HttpStatusCode.OK);
emptyResponse.Headers.Add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
emptyResponse.Headers.Add("Access-Control-Allow-Origin", ALLOWEDORIGIN);
emptyResponse.Headers.Add("Access-Control-Allow-Credentials", "true");
emptyResponse.Headers.Add("Access-Control-Allow-Headers", "Content-Type");
return emptyResponse;
}
else
{
task.Result.Headers.Add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
task.Result.Headers.Add("Access-Control-Allow-Headers", "Content-Type");
return task.Result;
}
});
}
}
Simply put, you can't use IHttpModule with self-hosted Web API, or anything that is not IIS. IHttpModule is an IIS concept only.
What you can do instead, as you have discovered, is you can modify the Web API pipeline and insert your code (or the Web API equivalent) there instead. This could be done with a DelegatingHandler, or an action filter.
The simplest solution, however, would be to simply use the Microsoft.AspNet.WebApi.Cors NuGet package. With an HttpConfiguration object, call .EnableCors(...) and pass in an EnableCorsAttribute object as per the instructions here from Microsoft.
This is what you've already done in your code above, but you seem to be trying to also add in the custom CORS code from your HTTP module. If you remove the EnableCors attribute from your controller, and remove your CustomHeaderHandler, it should work as you would expect.