I'm having a problem with consuming OData Services using Breeze, I set up a Web API OData service by following this guide, from Fiddler it works excellent as expected, but when I try to use it with breeze it fails and gives an error message of "OK":
[Q] Unhandled rejection reasons (should be empty):Error: OK
Using fiddler I see it goes and queries for metadata and then it queries for the entities which are returned correctly, what could be the problem here?
breeze.config.initializeAdapterInstances({ dataService: "OData" });
var manager = new breeze.EntityManager(serverAddress);
var query = new breeze.EntityQuery.from("Laboratories");
manager.executeQuery(query).then(function (data) {
ko.applyBindings(data);
}).fail(function (e) {
alert(e);
});
I enabled CORS by using the nightly build of ASP.NET Web API CORS support, it all works fine and I can retrieve the entities since I can see in fiddler that they are returned ... it's just that it doesn't go to the then promise instead it lands in fail.
UPDATE:
In response to #Ward testing from newly created projects I did the following:
PROJECT 1
Created a Web API Project.
Added Microsoft ASP.MET Web API Cross-Origin Resource Sharing (CORS) Reference from Nuget.
Added the following Controller:
namespace CORSBreezeTest1.Controllers
{
public class ValuesController : EntitySetController<Value, int>
{
ValuesDbContext _context = new ValuesDbContext();
[Queryable]
public override IQueryable<Value> Get()
{
return _context.Values;
}
protected override Value GetEntityByKey(int key)
{
return _context.Values.Find(key);
}
protected override Value CreateEntity(Value entity)
{
Value value = _context.Values.Find(entity.Id);
if (value != null)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Conflict));
}
_context.Values.Add(entity);
_context.SaveChanges();
return entity;
}
protected override int GetKey(Value entity)
{
return entity.Id;
}
protected override void Dispose(bool disposing)
{
_context.Dispose();
base.Dispose(disposing);
}
}
}
And the following Code First Database:
namespace CORSBreezeTest1
{
public class ValuesDbContext : DbContext
{
public ValuesDbContext()
: base("DefaultConnection")
{
}
public DbSet<Value> Values { get; set; }
}
public class Value
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
}
}
Added the following lines in WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Default code left out here ...
config.Routes.MapODataRoute("Values", "odata", GetEdmModel());
config.EnableQuerySupport();
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
}
private static IEdmModel GetEdmModel()
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = "CORSBreezeTest1";
builder.EntitySet<Value>("Values");
return builder.GetEdmModel();
}
PROJECT 2
Then created another Web API Project.
Added Breeze for ASP.NET Web API Projects Nuget Package
Added datajs Nuget Package.
Added the following lines of code to Index.cshtml:
<p data-bind="visible: !results">Fetching data ... </p>
<ul data-bind="foreach: results, visible: results" style="display: none">
<li>
<span data-bind="text: Name"></span>
<span data-bind="text: Quantity"></span>
</li>
</ul>
#section Scripts {
<script src="~/Scripts/knockout-2.2.0.debug.js"></script>
<script src="~/Scripts/q.js"></script>
<script src="~/Scripts/datajs-1.1.0.js"></script>
<script src="~/Scripts/breeze.min.js"></script>
<script type="text/javascript">
$(function () {
breeze.config.initializeAdapterInstances({ dataService: "OData" });
var manager = new breeze.EntityManager("http://serverAddress/odata")
var query = new breeze.EntityQuery.from("Values");
manager.executeQuery(query).then(function (data) {
ko.applyBindings(data);
}).fail(function (e) {
alert(e);
});
});
</script>
}
Tested as is and it worked since both websites are on localhost.
Published PROJECT 1 to a web server so that the test will actually see different origins, and tested.
And this is what Nugget saw:
The first request headers are OPTIONS
OPTIONS /odata/Values HTTP/1.1
And the second request headers are GET
GET /odata/Values HTTP/1.1
And if I change my fail code to:
fail(function (e) {
ko.applyBindings(e.body.value);
});
And my knockout code to:
<p data-bind="visible: !$data">Fetching data ... </p>
<ul data-bind="foreach: $data, visible: $data" style="display: none">
<li>
<span data-bind="text: Name"></span>
<span data-bind="text: Quantity"></span>
</li>
</ul>
Voila! It came through with the data:
And this is what the Console saw:
SEC7118: XMLHttpRequest for http://serverAddress/odata/$metadata required Cross Origin Resource Sharing (CORS).
localhost:53317
SEC7119: XMLHttpRequest for http://serverAddress/odata/$metadata required CORS preflight.
localhost:53317
SEC7118: XMLHttpRequest for http://serverAddress/odata/Values required Cross Origin Resource Sharing (CORS).
localhost:53317
SEC7119: XMLHttpRequest for http://serverAddress/odata/Values required CORS preflight.
localhost:53317
[Q] Unhandled rejection reasons (should be empty):Error: OK
PROJECTS 1 & 2 using the BreezeControllerAttribute
If I in another test add a new controller following Breeze Nuget example and add Breeze for ASP.NET Web API project Nuget package and add the following controller:
namespace CORSBreezeTest1.Controllers
{
[BreezeController]
public class BreezeValuesController : ApiController
{
readonly EFContextProvider<ValuesDbContext> _context =
new EFContextProvider<ValuesDbContext>();
[HttpGet]
public string Metadata()
{
return _context.Metadata();
}
[HttpGet]
public IQueryable<Value> Values()
{
return _context.Context.Values;
}
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
return _context.SaveChanges(saveBundle);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
}
}
And then modify the client as following:
//breeze.config.initializeAdapterInstances({ dataService: "OData" });
var manager = new breeze.EntityManager("http://serverAddress/breeze/BreezeValues")
Then the requests change:
And everything works ... I'm not sure if in part is something that EntitySetController handles requests differently or is Breeze making different requests when changing the dataService.
I don't know the answer yet. But there are a couple of misunderstandings in your question. First, CORS is a browser convention. It is a (relatively) safe way to work around the browser "Same Origin" policy.
That's a browser policy. Fiddler is not a browser. It is not constrained by "Same Origin" policies and can happily read and write across domain. So you can't tell if the server is properly configured for CORS based on what happens in Fiddler.
And of course "the same code in the same site that hosts the Web API works flawlessly"; you aren't violating the "Same Origin" policy so CORS is not involved.
You need to test your server configuration by writing a browser client application that launches from a site other than the host of your Web API. It doesn't have to be a breeze client. A simple AJAX call to an endpoint will do. You might as well write another simple, non-Breeze Web API controller while you're at it. Keep both controllers really simple. Keep your test client really simple.
My bet is that you will have the same trouble with both breeze-enabled and vanilla Web API controller. You'll power through that. And when you do, it should work for both the breeze and vanilla controllers. If you can show that your client works with one but not the other, come back and give us the code.
Sorry for your pains.
The only way to get it to work was to use the BreezeControllerAttribute from Breeze.WebApi following the breeze exact way of using the api. Not using EntitySetController and going back to a regular ApiController. Detailed explanation in the question itself.
[BreezeController]
public class BreezeValuesController : ApiController
{
// Methods here
}
You just need to add this extra parameter DataServiceVersion, MaxDataServiceVersion configuring enableCors.
config.EnableCors(new EnableCorsAttribute("*", "*", "*", "DataServiceVersion, MaxDataServiceVersion"));
Related
I get an error that says "ISession does not contain a definition for 'Abandon' and no accessible extension method 'Abandon' accepting a first argument of type 'ISession' could be found".
I have tried using session.clear but even after logging out if I open the website the user is logged in.
This is the error I get
This is how I have implemented Session in my ASP .NET CORE project:
Create a SessionTimeout filter:
public class SessionTimeout : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Session == null ||!context.HttpContext.Session.TryGetValue("UserID", out byte[] val))
{
context.Result =
new RedirectToRouteResult(new RouteValueDictionary(new
{
controller = "Pages",
action = "SessionTimeout"
}));
}
base.OnActionExecuting(context);
}
}
Register this filter in your Startup.cs:
In your ConfigureServices method:
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(10);
});
In your Configure add:
app.UseSession();
And finally decorate your class/method with your filter like:
[SessionTimeout]
public class DashboardController : Controller
To destroy your session based on Logout event from your View:
public IActionResult Logout()
{
HttpContext.Session.Clear();
return RedirectToAction("Login", new { controller = "Pages" });
}
It seems my session is being stored in cookies and not getting cleared/deleted when used session.clear()
so I've used this and it seems to work like a charm.
foreach (var cookie in Request.Cookies.Keys)
{
if (cookie == ".AspNetCore.Session")
Response.Cookies.Delete(cookie);
}
HttpContext.Session.Clear() wasn't working for me on my live site in the Controller for my Account/Logout page.
I found out that setting a href of /Account/Logout/ was the problem. I changed my links to /Account/Logout.
If you're having Session problems in .NET Core 3.1 then try this. It may also account for why I couldn't get Cookie Authentication to work - I gave up in the end and switched to using Sessions.
I am currently using python requests to consume a web api written in c# within my MVC application. This is the error I get
<body>
<div id="header"><h1>Server Error</h1></div>
<div id="content">
<div class="content-container"><fieldset>
<h2>405 - HTTP verb used to access this page is not allowed.</h2>
<h3>The page you are looking for cannot be displayed because an invalid method (HTTP verb) was used to attempt access.</h3>
</fieldset></div>
</div>
</body>
and here is the python code I am using
constURLString = "http://10.1.30.15/test/api/DevicesAPI"
send = requests.put(constURLString + "/" + str(deviceID), data = urlParam)
urlParam is the model I am passing back to the webapi in order to edit that specific entry. I tried typing the actual url into my browser trying to get that single entry by typing http://10.1.30.15/test/api/DevicesAPI/16 and got
<Error><Message>An error has occurred.</Message></Error>
Which isn't very helpful. This seems to work perfectly when I am running it locally but doesn't seem to like it when I publish it to the server. My put method within my web api goes as is:
// PUT: api/DevicesAPI/5
[ResponseType(typeof(void))]
public IHttpActionResult PutDevices(long id, Devices devices)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != devices.deviceID)
{
return BadRequest();
}
db.Entry(devices).State = EntityState.Modified;
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
if (!DevicesExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
I have a web site project ( the one that has an App_Code folder) which I have upgraded to .Net 4.5, and installed NuGet package for Web API 2.2 into the solution in VS 2013.
Under the root folder, there is a folder 'Vendors'. From a page under this folder, I am using jQuery to call a PUT Web API method inside a controller class 'AppsProcureWebApiController' under App_Code folder.
Using the url: 'api/AppsProcureWebApi' in ajax call from jQuery always results in a 'Not Found error'.
But if I hard-code the url as url:'http://localhost/appsprocure/api/AppsProcureWebApi' in same jQuery ajax call then it works and executes the code within the Web API method.
Question: Do I need to use some special routing configuration in global.asax to make it work with orginal url, Or there is something else I need to do? (code being used for configuring routing in global.asax is mentioned below).
jQuery for calling Web API from /Vendors/EditProduct.aspx page
function SaveProdDesc() {
var data = {
productId: $find("<%= radcomBoxProducts.ClientID%>").get_value(),
productDescription: $("#<%= txtProdDesc.ClientID%>").val(),
originalProductDescription: $("#<%= hfOrigProdDesc.ClientID%>").val()
}
$.ajax({
url: 'api/AppsProcureWebApi',
type: 'PUT',
data: JSON.stringify(data),
contentType: "application/json",
dataType:"json",
success: function (data) {
alert(data);
},
error: function (x, y, z) {
alert(x + '\n' + y + '\n' + z);
}
});
}
Routing defined in Global.asax
void Application_Start(object sender, EventArgs e)
{
//below code to make Web API work in Webforms
RouteTable.Routes.MapHttpRoute( name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = System.Web.Http.RouteParameter.Optional });
}
Web API controller class under App_Code is as below.
public class AppsProcureWebApiController : ApiController
{
//OTHER METHODS ARE OMITTED TO SAVE SPACE
[HttpPut]
[Authorize]
public int Put(ProductDesc p)
{
string prodDesc = p.ProductDescription;
return ApplicationDataAccess.UpdateProductDescription(p.ProductId, ref prodDesc, p.OriginalProductDescription);
}
}
public class ProductDesc
{
public long ProductId { get; set; }
public string ProductDescription { get; set; }
public string OriginalProductDescription { get; set; }
}
I found the answer after a lot of trying. This will be very useful in cases when using jQuery from an aspx page to call Web API in webforms asp.net projects, since in such projects the pages will exist under different folders.
Only a simple change is needed so the Web API can be called seamlessly from an aspx page under folder 'Vendors' using the url: 'api/AppsProcureWebApi'.
This simple change is of adding vendors to the routing configuration. If you let the original rule be there then make sure you name this new routing rule differently i.e. something other than DefaultApi. I have named this new rule as Rule1Api in code below.
So api/{controller}/{id} becomes vendors/api/{controller}/{id} in routing configuration as in code below. But do not change the url mentioned in jQuery call, which means let it be url: api/AppsProcureWebApi since vendors will be automatically prepended to the url mentioned in jQuery call.
void Application_Start(object sender, EventArgs e)
{
//below code to make Web API work in Webforms
RouteTable.Routes.MapHttpRoute( name: "Rule1Api",
routeTemplate: "vendors/api/{controller}/{id}",
defaults: new { id = System.Web.Http.RouteParameter.Optional });
}
Investigating the Web API as part of an MVC 4 project as an alternative way to provide an AJAX-based API. I've extended AuthorizeAttribute for the MVC controllers such that, if an AJAX request is detected, a JSON-formatted error is returned. The Web API returns errors as HTML. Here's the AuthorizeAttribute that I'm using with the MVC controllers:
public class AuthorizeAttribute: System.Web.Mvc.AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "area", "" },
{ "controller", "Error" },
{ "action", ( filterContext.HttpContext.Request.IsAjaxRequest() ? "JsonHttp" : "Http" ) },
{ "id", "401" },
});
}
}
How could I reproduce this to provide equivalent functionality for the Web API?
I realize that I need to extend System.Web.Http.AuthorizeAttribute instead of System.Web.Mvc.AuthorizeAttribute but this uses an HttpActionContext rather than an AuthorizationContext and so I'm stuck by my limited knowledge of the Web API and the seemingly incomplete documentation on MSDN.
Am I even correct in thinking that this would be the correct approach?
Would appreciate any guidance.
To get the equivalent functionality in a Web API filter you can set the HttpActionContext.Response property to an instance of HttpResponseMessage that has the right redirect status code and location header:
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext) {
var response = new HttpResponseMessage(HttpStatusCode.Redirect);
response.Headers.Location = new Uri("my new location");
actionContext.Response = response;
}
I would very much go with Marcin's answer - at the end of the day, he has written the code!
All I would add is that as Marcin is saying, your best bet is to have a dedicated controller to return the errors as appropriate - rather than setting the response code 401 with JSON content in the attribute.
The main reason is that Web API does the content-negotiation for you and if you want to do it yourself (see if you need to serve JSON or HTML) you lose all that functionality.
I've read many articles and several post (including here in stackoverflow) but do not know what I'm doing wrong.
Here my code:
Global.asax.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
ErrorControler.cs
public class ErrorController : Controller
{
public ActionResult Error404()
{
return View();
}
public ActionResult Error500()
{
return View();
}
}
Web.config
<customErrors mode="On" defaultRedirect="~/Error/Error500">
<error statusCode="404" redirect="~/Error/Error404"/>
</customErrors>
MyController.cs
public ActionResult Index()
{
using (var db = new DataContext())
{
int a = 2, b = 0;
var r = a / b;
return View(r);
}
}
Error500.cshtml
#model System.Web.Mvc.HandleErrorInfo
#{
ViewBag.Title = "Erro";
}
<h2>#ViewBag.Title</h2>
<div id="error-info">
<p>Ocorreu um erro inexperado na página <a class="error-url" href="#Response["aspxerrorpath"]" title="Origem do erro">#Response["aspxerrorpath"]</a></p>
<p>Se o erro persistir, por-favor, entre em contato com os administradores do site.</p>
<div class="error-details">
<p>#Model.Exception.Message</p>
</div>
</div>
When I try to access the path /MyController the following message appears:
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its
dependencies) could have been removed, had its name changed, or is
temporarily unavailable. Please review the following URL and make
sure that it is spelled correctly.
Requested URL: /Error/Error500
I would like that happen when an error on any controller, if the http status code has not been informed in web.config, it redirects to the default view Error500
In this article, for example, it handles DbException errors, but would like to handle any type of error.
Errors of type 404 (page not found) works perfectly. The user is redirected to the page Error404.cshtml
If you want this to happen remove/comment the following line from your Global.asax:
filters.Add(new HandleErrorAttribute());
You basically have to choose whether you want ASP.NET to handle your errors (the <customErrors> section in your web.config) or ASp.NET MVC (the global HandleErrorAttribute action filter, which by the way requires you to turn on custom errors in web.config)
Or checkout an alternative method for handling errors in ASP.NET MVC (with this approach you still have to remove the line I showed from your Global.asax).