Can't save the data to OData controller after updating to Microsoft.AspNet.WebApi.OData 5.3.x - breeze

Not sure what's changed exactly and causing this partial error (since it's not happening with retrieving the data) but after updating Microsoft.AspNet.WebApi.OData library to 5.3.x version, breeze has an issue with saving the data to OData controller. It's about the urls of the batch requests.
It can be reproduced with breeze's its own sample OData project as well;
http://www.breezejs.com/samples/breeze-web-api-odata
If you look at ExecuteRequestMessagesAsync method of the BatchHandler class, RequestUri property of the items contain OData route prefix two times.
Microsoft.AspNet.WebApi.OData library 5.2.2 url
http://localhost:55802/odata/TodoItems
Microsoft.AspNet.WebApi.OData library 5.3.1 url
http://localhost:55802/odata/odata/TodoItems
Any ideas how to solve this issue?
breeze version: 1.5.1

Oh joy. Microsoft has changed their Web API OData implementation ... again
Thanks for digging in, #coni2k, and identifying the problem.
Fortunately, you do NOT have to patch Breeze. We deliberately expose the getRoutePrefix method so you can change it externally yourself to meet your needs.
In the following example, I've incorporated your suggestion in the body of the method.
var adapter = breeze.config.getAdapterInstance('dataservice', 'webApiOdata');
adapter.getRoutePrefix = getRoutePrefix531; // plug-in alternative for THIS adapter instance.
function getRoutePrefix531(dataService) {
// Copied from breeze.debug and modified for Web API OData v.5.3.1.
if (typeof document === 'object') { // browser
var parser = document.createElement('a');
parser.href = dataService.serviceName;
} else { // node
parser = url.parse(dataService.serviceName);
}
// THE CHANGE FOR 5.3.1: Add '/' prefix to pathname
var prefix = parser.pathname;
if (prefix[0] !== '/') {
prefix = '/' + prefix;
} // add leading '/' (only in IE)
if (prefix.substr(-1) !== '/') {
prefix += '/';
} // ensure trailing '/'
return prefix;
};
As I write we're not sure how to detect which version of Web API OData you're talking to which makes it difficult for us to say a priori which version of getRoutePrefix is right for your application.
Hope to figure this out eventually. Don't dare change the default to this new version because that would break every existing app that has to talk to an older version of Web API OData. Not sure how we can win this game. We'll look at it. Frustrating for now.

Related

Async Function Fails when called as part of a Constructor

I'm rather new to Blazor, but I am currently trying to get access to some classes from within a class library that I've created and deployed as a Nuget package. As background, the Nuget package is an Api library, which allows me to talk to a webservice (I don't know if this is relevant or not). However, every time I go to the page where I'm testing, the page never loads and instead I left looking at the browser loading circle until I navigate away or close the application. During my testing here, it seems like it's the #inject call of my interface into the Blazor component which is causing the issue as when I remove it and try to load the page normally, the page does so.
So to demonstrate what I have setup, here is where I've added the Singletons to the DI:
builder.Services.AddSingleton<IApiConfigHelper, ApiConfigHelper>();
builder.Services.AddSingleton<IApiHelper, ApiHelper>();
builder.Services.AddSingleton<ISystemEndpoint, SystemEndpoint>();
Then on the blazor page, I have the following declarations at the top of my page:
#using Library.Endpoints
#using Library.Models
#page "/"
#inject ISystemEndpoint _systemEndpoint
Now I am leaning towards is this something to do with the Nuget package and using it with DI. I have tested the library away from this project (In a console application) and can confirm it's working as it should.
I have also created a local class library as a test to, to see if I could inject a data access class into the page and I can confirm that this works without an issue, which suggests to me that DI is working, just not with my Nuget package.
I did have a look into CORS, given that the Nuget package is accessing an external domain, and setup the following simple CORS policy in the app:
builder.Services.AddCors(policy =>
{
policy.AddPolicy("OpenCorsPolicy", opt =>
opt.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod());
});
Which is added to the app after the AddRouting call like so:
app.UseCors("OpenCorsPolicy");
However again, this wasn't the solution so if anyone is able to point me in the right direction with where I may be going wrong with this or offer any advice, I would be most grateful.
EDIT 1 - Provides details #mason queried
Regarding SystemEndpoint, the constructor is being injected with 2 things, as below:
public SystemEndpoint(IApiHelper apiHelper, IOptions<UriConfigModel> uriOptions)
{
_apiHelper = apiHelper;
_uriOptions = uriOptions.Value;
}
My Nuget Library is dependant on the following:
Azure.Identity
Azure.Security.KeyVault.Secrets
Microsoft.AspNet.WebApi.Client
Microsoft.Extensisons.Options.ConfigurationExtensions
EDIT 2 - Doing some further testing with this I have added a simple Endpoint class to my Nuget library, which returns a string with a basic message, as well as returning the values of the 2 UriConfig properties as below. I added this test to 1) sanity check that my DI was working correctly, and 2) check the values that are being assigned from appsettings to my UriConfig Object.
public class TestEndpoint : ITestEndpoint
{
private readonly IOptions<UriConfigModel> _uriConfig;
public TestEndpoint(IOptions<UriConfigModel> uriConfig)
{
_uriConfig = uriConfig;
}
public string TestMethod()
{
return $"You have successfully called the test method\n\n{_uriConfig.Value.Release} / {_uriConfig.Value.Version}";
}
}
However when adding in the dependency of IApiHelper into the Ctor, the method then breaks and fails to load the page. Looking into ApiHeloer, the Ctor has a dependency being injected into it of IApiConfigHelper. Looking at the implementation, the Ctor of ApiConfigHelper is setting up the values and parameters of the HttpClient that should make the REST calls to the external Api.
Now I believe what is breaking the code at this point is a call I'm making to Azure Key Vault, via REST, to pull out the secret values to connect to the Api. The call to KeyVault is being orchestrated via the following method, making use of the Azure.Security.KeyVault.Secrets Nuget Package, however I assume that at the heart of it, it's making a REST call to Azure on my behalf:
private async Task<KeyVaultSecret> GetKeyVaultValue(string secretName = "")
{
try
{
if (_secretClient is not null)
{
var result = await _secretClient.GetSecretAsync(secretName);
return result.Value;
}
}
catch (ArgumentException ae)
{
Console.WriteLine(ae.Message);
}
catch (Azure.RequestFailedException rfe)
{
Console.WriteLine(rfe.Message);
}
return new(secretName, "");
}
So that's where I stand with this at the moment. I still believe it could be down to CORS, as it seems to be falling over when making a call to an external service / domain, but I still can say 100%. As a closing thought, could it be something as simple as when I call call the above method, it's not being awaited????
So after persisting with this it seems like the reason it was failing was down to "awaiting" the call to Azure KeyVault, which was happening indirectly via the constructor of ApiConfigHelper. The resulting method for getting KeyVault value is now:
private KeyVaultSecret GetKeyVaultValue(string secretName = "")
{
try
{
if (_secretClient is not null)
{
var result = _secretClient.GetSecret(secretName);
if (result is not null)
{
return result.Value;
}
}
}
catch (ArgumentException ae)
{
Console.WriteLine(ae.Message);
}
catch (Azure.RequestFailedException rfe)
{
Console.WriteLine(rfe.Message);
}
return new(secretName, "");
}
I am now able to successfully make calls to my library and return values from the Api it interacts with.
I can also confirm that this IS NOT a CORS issue. Once I saw that removing the await was working, I then removed the CORS policy declarations from the service and the app in my Blazor's start-up code and everything continued to work without an issue.
As a final note, I must stress that this is only seems an issue when using the library with Blazor (possibly webApi projects) as I am able to use the library, awaiting the Azure call just fine in a console application.

Breeze.Sharp Cannot Import Metadata from Breeze Server for ASP.NET Core 3

I am trying to use breeze sharp with Blazor Webassembly.
I have been able to recompile breeze sharp for .Net Standard 2.0 and 2.1 that has shown positive results reading data from the server.
However when trying to load Metadata from script I have noticed that MetadaStore.ImportMetadata method fails to load metadata.
When I followed up I found that there Breeze.Sharp expects metadataVersion property and namingConvention node from the metadata json returned by the breeze server.
What I can see is that First the metadata format has changed and those properties are no longer present in the generated metadata.
But if I FetchMetadata using DataService then the metadata is loaded into the metadatastore.
My Question is ...Is there a plan to update breeze sharp to be aligned with recent developments in the dotnet platform?
Kindly consider it as it aligns very well with Blazor. Actually my experiment went very fine expect for loading metadata stored locally instead of loading it from the server every time.
private void DeserializeFrom(JNode jNode, bool isFromServer) {
MetadataVersion = jNode.Get<String>("metadataVersion");
// may be more than just a name
var ncNode = jNode.GetJNode("namingConvention");
if (ncNode != null) {
var nc = Configuration.Instance.FindOrCreateNamingConvention(ncNode);
if (nc == null) {
OnMetadataMismatch(null, null, MetadataMismatchTypes.MissingCLRNamingConvention, ncNode.ToString());
} else {
// keep any preexisting ClientServerNamespaceMap info
NamingConvention = nc.WithClientServerNamespaceMapping(this.NamingConvention.ClientServerNamespaceMap);
}
}
// localQueryComparisonOptions
jNode.GetJNodeArray("dataServices").Select(jn => new DataService(jn)).ForEach(ds => {
if (GetDataService(ds.ServiceName) == null) {
AddDataService(ds);
}
});
jNode.GetJNodeArray("structuralTypes")
.ForEach(jn => UpdateStructuralTypeFromJNode(jn, isFromServer));
jNode.GetMap<String>("resourceEntityTypeMap").ForEach(kvp => {
var stName = kvp.Value;
if (isFromServer) {
stName = TypeNameInfo.FromStructuralTypeName(stName).ToClient(this).StructuralTypeName;
}
// okIfNotFound because metadata may contain refs to types that were already excluded earlier in
// UpdateStructuralTypeFromJNode
var et = GetEntityType(stName, true);
if (et != null) {
SetResourceName(kvp.Key, et);
}
});
}
I have been following up very closely new developments and this is what I have noticed so far.
Microsoft is reviving the product called Microsoft Restier.
First it was developed on the .Net Framework Platform but now they are rewriting it to run on the .Net Core and is expected to go RTM in the first half 0f the year 2020. A good thing about this Microsoft Restier is that your Full Entity Context is exposed as OData endpoint with your entity lists exposed without the need for creating Controllers on Actions.
This way it reduces the tedious work of writing an action for each entity that you want to expose as an OData resource different from the current case with plain OData and Breeze Server.
There are so many areas for configurations and extensibility.
Microsoft is remaking RIA Data Services in the form of OData Connected Service.
This creates a Proxy in the same way as RIA Data Services.
With the Proxy generated there is no need to create a client data model. The model created on the server will suffice... not as the case for breeze sharp where you need to create a model on the client although there are signs that at DevForce they are exploring using PostSharp to make it possible to use POCO objects sharable between breeze server and clients. We however do not know when will this be available.
OData Connected Service works seamlessly with Blazor Server (On the Client Side there some bugs that are being worked on ) and removes the headache of working with bare bone HttpClient.
As for breeze sharp it currently works well with both Blazor Server and Web Assembly versions (Breeze Sharp Standard). I think they will rework their product and enable it to work with OData the same way Breezejs does. This combined with Microsoft.Restier will make life very easy.
Actually breeze has very nice features especially when it comes to caching.
Lets wait and see.

How do i solve '"Reference to type 'BaseControlContext" claim.....' for AspNet.Security.OpenIdConnect.Server

I am facing weird issue.
I am reading and creating OpenID Connect server with ASOS this article ASOS - AspNet.Security.OpenIdConnect.Server.
I simply created new sample solution and added new subclass AuthorizationProvider class of OpenIdConnectServerProvider and override the virtual method i.e. ExtractAuthorizationRequest
AuthorizationProvider.cs
public class AuthorizationProvider : OpenIdConnectServerProvider
{
public override Task ExtractAuthorizationRequest(ExtractAuthorizationRequestContext context)
{
// If a request_id parameter can be found in the authorization request,
// restore the complete authorization request stored in the user session.
if (!string.IsNullOrEmpty(context.Request.RequestId))
{
var payload = context.HttpContext.Session.Get(context.Request.RequestId);
if (payload == null)
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Invalid request: timeout expired.");
return Task.FromResult(0);
}
// Restore the authorization request parameters from the serialized payload.
using (var reader = new BsonReader(new MemoryStream(payload)))
{
foreach (var parameter in JObject.Load(reader))
{
// Avoid overriding the current request parameters.
if (context.Request.HasParameter(parameter.Key))
{
continue;
}
context.Request.SetParameter(parameter.Key, parameter.Value);
}
}
}
return Task.FromResult(0);
}
}
Issue:
As soon as i add Microsoft.AspNetCore.Identity (2.0.0) NuGet package to my project, context.Reject start giving the following error
"Reference to type 'BaseControlContext" claim it is defined in
Microsoft.AspNetCore.Authentication, but it could not be found.
But as soon as I remove Microsoft.AspNetCore.Identity NuGet dependency, the error goes away and all looks fine.
Note:
I am using VS 2017.
I am using dotnetcore 2.0 SDK.
I created solution using .Net Core 2.0.
Massive changes have been introduced in ASP.NET Core 2.0's authentication stack. The changes are so important that all the authentication middleware written for ASP.NET Core 1.x are not compatible (which includes all the aspnet-contrib projects).
You can read https://github.com/aspnet/Announcements/issues/262 for more information.
The good news is that we have an ASP.NET Core 2.0 RTM-compatible version of ASOS. You can find the 2.0.0-preview1-* bits on the aspnet-contrib MyGet feed (https://www.myget.org/F/aspnet-contrib/api/v3/index.json).

breeze 1.3.6 cannot get metadata

The Devcurry hottowelsignalr example which utilizes breeze works fine until i upgraded breeze via nuget to 1.3.6.
F12 in chrome shows that it is trying to retrieve metadata from http://localhost/api/breeze/Metadata whereas previously it would use
http://localhost/OnlineCollaborationWithSignalR/api/breeze/Metadata. Which is the correct location of the call. It appears that the path of the application is missing from the root. i.e. (OnlineCollaborationWithSignalR)
Update
noticed the release notes for 1.3.1. And subsequently changed the routing to
public static class BreezeWebApiConfig {
public static void RegisterBreezePreStart() {
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "BreezeApi",
routeTemplate: "breeze/{controller}/{action}"
);
}
}
and the app/viewmodels/home.js to
// service name is route to the Web API controller
var serviceName = 'breeze/Breeze';
from
// service name is route to the Web API controller
var serviceName = 'api/Breeze';
It still fails with the same error as noted above.
Can you confirm that the API responds accordingly?
Based on your routeTemplate, it should be.
http://localhost/breeze/Breeze/Metadata
UPDATE:
Yes, if your configuration is such. It should be
http://localhost/OnlineCollaborationWithSignalR/breeze/Breeze/Metadata
I am not familiar with the specific project, but I downloaded and updated it. I then made the changes that you specified, and I am getting a response from the API. Although I am getting an error (most likely unrelated), it is in fact routed properly.
For the record, the error I get is:
"The provider for invariant name 'System.Data.SqlClient' is specified multiple times in the application configuration. The invariant name must be unique for each configured provider."

Best way to structure the code for an ASP.NET MVC REST API that is decoupled from the data formats?

I am creating a REST API in ASP.NET MVC. I want the format of the request and response to be JSON or XML, however I also want to make it easy to add another data format and easy to create just XML first and add JSON later.
Basically I want to specify all of the inner workings of my API GET/POST/PUT/DELETE requests without having to think about what format the data came in as or what it will leave as and I could easily specify the format later or change it per client. So one guy could use JSON, one guy could use XML, one guy could use XHTML. Then later I could add another format too without having to rewrite a ton of code.
I do NOT want to have to add a bunch of if/then statements to the end of all my Actions and have that determine the data format, I'm guessing there is some way I can do this using interfaces or inheritance or the like, just not sure the best approach.
Serialization
The ASP.NET pipeline is designed for this. Your controller actions don't return the result to the client, but rather a result object (ActionResult) which is then processed in further steps in the ASP.NET pipeline. You can override the ActionResult class. Note that FileResult, JsonResult, ContentResult and FileContentResult are built-in as of MVC3.
In your case, it's probably best to return something like a RestResult object. That object is now responsible to format the data according to the user request (or whatever additional rules you may have):
public class RestResult<T> : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
string resultString = string.Empty;
string resultContentType = string.Empty;
var acceptTypes = context.RequestContext.HttpContext.Request.AcceptTypes;
if (acceptTypes == null)
{
resultString = SerializeToJsonFormatted();
resultContentType = "application/json";
}
else if (acceptTypes.Contains("application/xml") || acceptTypes.Contains("text/xml"))
{
resultString = SerializeToXml();
resultContentType = "text/xml";
}
context.RequestContext.HttpContext.Response.Write(resultString);
context.RequestContext.HttpContext.Response.ContentType = resultContentType;
}
}
Deserialization
This is a bit more tricky. We're using a Deserialize<T> method on the base controller class. Please note that this code is not production ready, because reading the entire response can overflow your server:
protected T Deserialize<T>()
{
Request.InputStream.Seek(0, SeekOrigin.Begin);
StreamReader sr = new StreamReader(Request.InputStream);
var rawData = sr.ReadToEnd(); // DON'T DO THIS IN PROD!
string contentType = Request.ContentType;
// Content-Type can have the format: application/json; charset=utf-8
// Hence, we need to do some substringing:
int index = contentType.IndexOf(';');
if(index > 0)
contentType = contentType.Substring(0, index);
contentType = contentType.Trim();
// Now you can call your custom deserializers.
if (contentType == "application/json")
{
T result = ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(rawData);
return result;
}
else if (contentType == "text/xml" || contentType == "application/xml")
{
throw new HttpException(501, "XML is not yet implemented!");
}
}
Just wanted to put this on here for the sake of reference, but I have discovered that using ASP.NET MVC may not be the best way to do this:
Windows Communication Foundation (WCF)
provides a unified programming model
for rapidly building service-oriented
applications that communicate across
the web and the enterprise
Web application developers today are
facing new challenges around how to
expose data and services. The cloud,
move to devices, and shift toward
browser-based frameworks such as
jQuery are all placing increasing
demands on surfacing such
functionality in a web-friendly way.
WCF's Web API offering is focused on
providing developers the tools to
compose simple yet powerful
applications that play in this new
world. For developers that want to go
further than just exposing over HTTP,
our API will allow you to access all
the richness of HTTP and to apply
RESTful constraints in your
application development. This work is
an evolution of the HTTP/ASP.NET AJAX
features already shipped in .Net 4.0.
http://wcf.codeplex.com/
However I will not select this as the answer because it doesn't actually answer the question despite the fact that this is the route I am going to take. I just wanted to put it here to be helpful for future researchers.

Resources