ASP.NET Odata Web API Composable function issue - c#

I am building a Service Layer in Web API OData that exposes a file management API. I have a problem with composable functions. Consider the following scenario. Particular files can be accessed in two ways: through an ID or through a complex Path. My original design concept was to have two URLS:
/File({IdAsGuid})
/Repositories({RepositoryName})/Libraries({libName})/Path({path})/api.getFileByName(name={fileName})
This worked pretty well using the ODataRoute attributes. The next step was to support versions, which would use URL's like:
/File({IdAsGuid})/Versions({versionNumber})
/Repositories({RepositoryName})/Libraries({libName})/Path({path})/api.getFileByName(name={fileName})/Versions({versionNumber})
Using an EntitySet "Versions" as a path segment was no problem or the first URL. However, OData refused to validate the EntitySet used after the function call. The error:
The segment 'eBesNg.getContentByName' must be the last segment in the
URI because it is one of the following: $ref, $batch, $count, $value,
$metadata, a named media resource, an action, a noncomposable
function, an action import, a noncomposable function import, an
operation with void return type, or an operation import with void
return type.
After some research, I realized that the function is defined as follows:
builder.Namespace = "api";
var function = builder.EntityType<Path>().Function("getFileByName");
function.Parameter<string>("name");
function.ReturnsFromEntitySet<File>("Files");
And may additionally require:
function.IsComposable = true;
However, this created a different issue. Now, during the OData validation, I receive a NullReferenceException:
[NullReferenceException: Object reference not set to an instance of an object.]
Microsoft.OData.Core.UriParser.Parsers.ODataPathParser.CreatePropertySegment(ODataPathSegment
previous, IEdmProperty property, String queryPortion) +205
Microsoft.OData.Core.UriParser.Parsers.ODataPathParser.CreateNextSegment(String
text) +405
Microsoft.OData.Core.UriParser.Parsers.ODataPathParser.ParsePath(ICollection'1
segments) +244
Microsoft.OData.Core.UriParser.Parsers.ODataPathFactory.BindPath(ICollection'1
segments, ODataUriParserConfiguration configuration) +96
Microsoft.OData.Core.UriParser.ODataUriParser.ParsePathImplementation()
+205
What am I missing? Is it not possible to use functions for navigation and continue to navigate on results in OData?

You should properly set the EntitySetPath of your function. That is, replace:
function.ReturnsFromEntitySet<File>("Files");
With
function.ReturnsEntityViaEntitySetPath<File>("bindingParameter/xxx");
Here is a complete sample:
class Path
{
public string Id { get; set; }
public File File { get; set; }
}
class File
{
public Guid Id { get; set; }
public ICollection<Version> Versions { get; set; }
}
class Version
{
public string Id { get; set; }
}
class Program
{
static void Main(string[] args)
{
var builder = new ODataConventionModelBuilder();
builder.Namespace = "api";
builder.EntityType<File>();
var function = builder.EntityType<Path>().Function("getFileByName");
function.Parameter<string>("name");
//function.ReturnsFromEntitySet<File>("Files");
function.ReturnsEntityViaEntitySetPath<File>("bindingParameter/File");
function.IsComposable = true;
builder.EntitySet<Path>("Paths");
builder.EntitySet<Version>("Versions");
var model = builder.GetEdmModel();
string path = "Paths('1')/api.getFileByName(name='sd')/Versions('s')";
var parser = new ODataUriParser(model, new Uri(path, UriKind.Relative));
var pa = parser.ParsePath();
Console.WriteLine(pa);
}
}

Related

How does .NET Core C# Minimal API fill parameters?

With minimalistic API how does MapGet automatically fill parameters from querystring?
With minimalistic API the following is possible:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("api/Students/Grades", StudentsDataContext.GetGradesAsync).RequireAuthorization("Admin");
//...
public class Grade
{
public string? Subject { get; set; }
public int GradePercentage { get; set; } = 0;
}
public class StudentsDataContext
{
public static async Task<List<Grade>> GetGradesAsync(int? studentId, ClaimsPrincipal user, CancellationToken ct))
{
// Gets grades from database...
return new List<Grade>() {
new () { Subject = "Algebra", GradePercentage=95 },
new () { Subject = "English", GradePercentage=90 }
};
}
}
When you call: /api/Students/Grades?studentId=5
magically, studentId is passed to the GetGradesAsync, as well as ClaimsPrinicipal, and CancellationToken.
How does this witchcraft work? Is it possible to learn this power of the darkside?
The link you have provided describes the rules of the parameter binding in Minimal APIs. In the nutshell it is pretty simple - the request handler delegate is analyzed (via some reflection+runtime code generation or source generation at build time I suppose) and actual handler is created were all parameters are processed accordingly and passed to user defined one.
I have not spend much time so the following can be not entirely correct, but the starting point of the investigation how does it actually work would be EndpointRouteBuilderExtensions which leads to RequestDelegateFactory (see AddRouteHandler call which fills List<RouteEntry> _routeEntries which is later processed with CreateRouteEndpointBuilder method, which calls CreateHandlerRequestDelegate) which should contain all the actual "magic".

How to add and retrive additional information for Azure Blob

Is there a way to add additional information for blob in Azure?
I want to store some relevant information, which connects the blob to other entity in in an a document database, for example a string which contains a JSON.
I know there is metadata for blob when I use Azure Storage explorer, but i want use it from code.
This a relevant question about this theme:
Adding Description/Metadata to Azure Blob
And how can retrieve the blobs based on this metadata?
Have you checked this link?
public static async Task AddContainerMetadataAsync(CloudBlobContainer container)
{
// Add some metadata to the container.
container.Metadata.Add("docType", "textDocuments");
container.Metadata["category"] = "guidance";
// Set the container's metadata.
await container.SetMetadataAsync();
}
Keep in mind that
The name of your metadata must conform to the naming conventions for C# identifiers.
The first part of the question is answered by Mihail Stancescu, thank you!
The second part is not answered correctly yet. The Azure Search is a solution for it, but it is a totally other service. I want to solve this problem in my repository class. And i solved it.
Maybe it is interesting for someone else that is why I share my solution:
Behind the solution
There is a metadata in AzureBlob, which has a string type. I serialized a object to String and store this string in metadata. When I need this information in any cases, I listing the with metadata in it. I reach this this functionality to passing the Microsoft.WindowsAzure.Storage.Blob.BlobListingDetails.Metadata value to the blobListingDetails parameter in ListBlobs function.
When The blobs are arrived, I inmediatly Deserialized back from JSON to object. This mechasim is visible in LINQ Select:
.Select<Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob, T>(blob = > JsonConvert.DeserializeObject<T>(blob.Metadata["data"]))
After this, the LINQ type is T, so I can Apply the Expression on it in LINQ Where.
The complete solution is:
GetMany function
public IEnumerable<T> GetMany( Expression<Func<T, bool>> filter )
{
return _AzureBlobCollection.BlobDirectory
.ListBlobs( useFlatBlobListing: false, blobListingDetails: Microsoft.WindowsAzure.Storage.Blob.BlobListingDetails.Metadata )
.OfType<Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob>()
.Select<Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob, T>( blob => JsonConvert.DeserializeObject<T>( blob.Metadata[ "data" ] ) )
.Where( filter.Compile() );
}
This function can call like this:
Repository repository = ..
IEnumerable files = repository.GetMany( f => f.Partner = "Microsoft" );
Base classes
where file class is:
public class ContractFile : File
{
public string Partner { get; set; }
public Date CreationDate { get; set; }
public string Remarks { get; set; }
public string Filename { get; set; }
}
...
public class File
{
public String File { get; set; }
public Stream Data { get; set; }
}
And the insert is following:
public void AddOne( T file )
{
file.id = Guid.NewGuid().ToString();
Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob blob = _AzureBlobCollection.BlobDirectory.GetBlockBlobReference( file.id );
blob.UploadFromStream( file.Data );
blob.Metadata.Add( "data", JsonConvert.SerializeObject( file ) );
blob.SetMetadata();
}

Can't get any documents with NEST from elasticsearch

I use Searchblox to index and search my files, which itself calls ES 2.x to do the job. Searchblox uses a "mapping.json" file to initialize a mapping upon the creation of an index. Here's the link to that file. As "#Russ Cam" suggested here, I created my own class content with the following code (just like he did with the "questions" index and "Question" class):
public class Content
{
public string type { get; set; }
public Fields fields { get; set; }
}
public class Fields
{
public Content1 content { get; set; }
public Autocomplete autocomplete { get; set; }
}
public class Content1
{
public string type { get; set; }
public string store { get; set; }
public string index { get; set; }
public string analyzer { get; set; }
public string include_in_all { get; set; }
public string boost { get; set; }
} //got this with paste special->json class
These fields from the content class (type,store etc.) come from the mapping.json file attached above. Now, when I (just like you showed me) execute the following code:
var searchResponse = highLevelclient.Search<Content>(s => s.Query(q => q
.Match(m => m.Field(f => f.fields.content)
.Query("service")
All I get as a response on the searchResponse variable is:
Valid NEST response built from a successful low level call on POST: /idx014/content/_search
Audit trail of this API call:
-HealthyResponse: Node: http://localhost:9200/ Took: 00:00:00.7180404
Request:
{"query":{"match":{"fields.content":{"query":"service"}}}}
Response:
{"took":1,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":0,"max_score":null,"hits":[]}}
And no documents in searchResponse.Documents. Contradictorily, when I search for the "service" query on Searchblox or make an API call to localhost:9200 with the Sense extension of Google Chrome, I get 2 documents. (the documents that I was looking for)
In brief, all I want is to be able to :
get all the documents (no criteria)
get all the documents within a time range and based upon keywords.. such as "service"
What am I doing wrong? I can provide with more information if needed.. Thank you all for your detailed answers.
Your C# POCO is not correct in regards to your mapping; your document type is "sdoc" and each of the properties under the "properties" property is a field on that document type; These fields map to properties on your C# POCO.
As an example to get you started
public class Document
{
[String(Name = "uid")]
public string UId { get; set; }
public string Content { get; set; }
}
NEST by default will camel case POCO property names, so "content" will be case correctly according to your mapping, however, we use attribute mapping for the "uid" field in order to name it to match the mapping (we can go further here and set additional attribute property values to fully match the mapping; see the automapping documentation).
Now, to search with the document, let's create the connection settings and a client to use
void Main()
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var connectionSettings = new ConnectionSettings(pool)
.InferMappingFor<Document>(t => t
// change the index name to the name of your index :)
.IndexName("index-name")
.TypeName("sdoc")
.IdProperty(p => p.UId)
);
var client = new ElasticClient(connectionSettings);
// do something with the response
var searchResponse = client.Search<Document>(s => s
.Query(q => q
.Match(m => m
.Field(f => f.Content)
.Query("service")
)
)
);
}
We set up the client with some inference rules for the Document type which will be used when interacting with Elasticsearch. The above query emits the following query json
{
"query": {
"match": {
"content": {
"query": "service"
}
}
}
}
As an aside, I noticed that the mapping contained a multi_field type; multi_field types were removed in Elasticsearch 1.0 (multi fields are still there, just the actual type is not), so be sure that you're actually running Elasticsearch 2.x on Searchblox, as NEST 2.x is only supported against Elasticsearch 2.x.

ServiceStack support for conditionally omitting fields from a REST response on a per-call basis

<TL;DR>
At a minimum, I'm looking for a way to conditionally exclude certain properties on the resource from being included in the response on a per-call basis (See fields below).
Ideally, I'd like to implement a REST service with ServiceStack that supports all the major points below.
UPDATE
While I really like ServiceStack's approach in general and would prefer to use it if possible, if it isn't particularly well suited towards these ideas I'd rather not bend over backwards bastardizing it to make it work. If that's the case, can anyone point to another c# framework that might be more appropriate? I'm actively exploring other options myself, of course.
</TD;DR>
In this talk entitled Designing REST + JSON APIs, the presenter describes his strategy for Resource References (via href property on resources) in JSON. In addition to this, he describes two query parameters (fields and expand) for controlling what data is included the response of a call to a REST service. I've been trying without success to dig into the ServiceStack framework to achieve support for fields in particular but have thus far been unsuccessful. Is this currently possible in ServiceStack? Ideally the solution would be format agnostic and would therefore work across all of ServiceStack's supported output formats. I would imagine expand would follow the same strategy.
I'll describe these features here but I think the talk at the link does a better job of explaining them.
Lets say we have an Profiles resource with the following properties: givenName, surname, gender, and favColor. The Profiles resource also includes a list of social networks the user belongs to in the socialNetworks property.
href - (42:22 in video) Every resource includes a full link to it on the REST service. A call to GET /profiles/123 would return
{
"href":"https://host/profiles/123",
"givenName":"Bob",
"surname":"Smith",
"gender":"male",
"favColor":"red",
"socialNetworks": {
"href":"https://host/socialNetworkMemberships?profileId=123"
}
}
Notice that the socialNetworks property returns an object with just the href value populated. This keeps the response short and focused while also giving the end user enough information to make further requests if desired. The href property, used across the board in this manor, makes it easy (conceptually anyway) to reuse resource data structures as children of other resources.
fields - (55:44 in video) Query string parameter that instructs the server to only include the specified properties of the desired resource in the REST response.
A normal response from GET /profiles/123 would include all the properties of the resource as seen above. When the fields query param is included in the request, only the fields specified are returned. 'GET /propfiles/123?fields=surname,favColor' would return
{
"href":"https://host/profiles/123",
"surname":"Smith",
"favColor":"red"
}
expand - (45:53 in video) Query string parameter that instructs the server to flesh out the specified child resources in the result. Using our example, if you were to call GET /profiles/123?expand=socialNetworks you might receive something like
{
"href":"https://host/profiles/123",
"givenName":"Bob",
"surname":"Smith",
"gender":"male",
"favColor":"red",
"socialNetworks": {
"href":"https://host/socialNetworkMemberships?profileId=123",
"items": [
{
"href":"https://host/socialNetworkMemberships/abcde",
"siteName":"Facebook",
"profileUrl":"http://www.facebook.com/..."
},
...
]
}
}
So...in my opinion ServiceStack's best feature is that it makes sending, receiving and handling POCOs over HTTP super easy. How you set up the POCOs and what you do in between (within the 'Service') is up to you. Does SS have opinions? Yes. Do you have to agree with them? No. (But you probably should :))
I think expanding on something like below would get you close to how you want to handle your api. Probably not the best example of ServiceStack but the ServiceStack code/requirements are barely noticeable and don't get in your way (AppHost configure not shown). You could probably do something similar in other .NET Frameworks (MVC/Web API/etc) but, in my opinion, won't look as much like straight C#/.NET code as with ServiceStack.
Request classes
[Route("/Profiles/{Id}")]
public class Profiles
{
public int? Id { get; set; }
}
[Route("/SocialNetworks/{Id}")]
public class SocialNetworks
{
public int? Id { get; set; }
}
Base Response class
public class BaseResponse
{
protected virtual string hrefPath
{
get { return ""; }
}
public string Id { get; set; }
public string href { get { return hrefPath + Id; } }
}
Classes from example
public class Profile : BaseResponse
{
protected override string hrefPath { get { return "https://host/profiles/"; } }
public string GivenName { get; set; }
public string SurName { get; set; }
public string Gender { get; set; }
public string FavColor { get; set; }
public List<BaseResponse> SocialNetworks { get; set; }
}
public class SocialNetwork: BaseResponse
{
protected override string hrefPath { get { return "https://host/socialNetworkMemberships?profileId="; }}
public string SiteName { get; set; }
public string ProfileUrl { get; set; }
}
Services
public class ProfileService : Service
{
public object Get(Profiles request)
{
var testProfile = new Profile { Id= "123", GivenName = "Bob", SurName = "Smith", Gender = "Male", FavColor = "Red",
SocialNetworks = new List<BaseResponse>
{
new SocialNetwork { Id = "abcde", SiteName = "Facebook", ProfileUrl = "http://www.facebook.com/"}
}
};
if (!String.IsNullOrEmpty(this.Request.QueryString.Get("fields")) || !String.IsNullOrEmpty(this.Request.QueryString.Get("expand")))
return ServiceHelper.BuildResponseObject<Profile>(testProfile, this.Request.QueryString);
return testProfile;
}
}
public class SocialNetworkService : Service
{
public object Get(SocialNetworks request)
{
var testSocialNetwork = new SocialNetwork
{
Id = "abcde",
SiteName = "Facebook",
ProfileUrl = "http://www.facebook.com/"
};
if (!String.IsNullOrEmpty(this.Request.QueryString.Get("fields")) || !String.IsNullOrEmpty(this.Request.QueryString.Get("expand")))
return ServiceHelper.BuildResponseObject<SocialNetwork>(testSocialNetwork, this.Request.QueryString);
return testSocialNetwork;
}
}
Reflection Helper Class
public static class ServiceHelper
{
public static object BuildResponseObject<T>(T typedObject, NameValueCollection queryString) where T: BaseResponse
{
var newObject = new ExpandoObject() as IDictionary<string, object>;
newObject.Add("href", typedObject.href);
if (!String.IsNullOrEmpty(queryString.Get("fields")))
{
foreach (var propertyName in queryString.Get("fields").Split(',').ToList())
{
//could check for 'socialNetwork' and exclude if you wanted
newObject.Add(propertyName, typedObject.GetType().GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).GetValue(typedObject, null));
}
}
if (!String.IsNullOrEmpty(queryString.Get("expand")))
{
foreach (var propertyName in queryString.Get("expand").Split(',').ToList())
{
newObject.Add(propertyName, typedObject.GetType().GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).GetValue(typedObject, null));
}
}
return newObject;
}
}
Usually you can control the serialization of your DTOs by setting the DataMember attributes. With those attributes you can control if the property should have defaults or not.
Meaning if you simply do not define the property of the object you want to return, it should not be serialized and therefore will not be shown in the resulting Json.
ServiceStack internally uses the standard DataContract...Serializer, so this should be supported
Otherwise you could also make use of dynamic objects and simply compose your object at runtime, serialize it and send it back.
Here is a very very basic example:
var seri = JsonSerializer.Create(new JsonSerializerSettings() { });
using (var textWriter = new StringWriter())
{
var writer = new JsonTextWriter(textWriter);
dynamic item = new { Id = id };
seri.Serialize(writer, item);
return textWriter.ToString();
}

Hypermedia links with Servicestack new API

I am evaluating how to add hypermedia links to DTO responses. Although there is no standard, add List to the response DTOs seems to be the suggested approach.
Do you know of any example or reference of implementation using ServiceStack framework?
Adding List is ok for me, but my doubts are about where to put the logic of the following links (Within the service or a specialized class that holds the state machine?) and where to resolve the routes (A filter?)
Thanks.
[Update] From ServiceStack version v3.9.62 it is posible to access Routes configuration via EndpointHost.Config.Metadata.Routes.RestPath, so the solution provided by tgmdbm can be improved withouth the need of "IReturn + Routes attributes", just using Metadata.Routes information.
In fact all service metadata can be queried and used to cross-cutting concerns. Servicestack rocks.
The way I do this currently is I pass back a response dto which implements an interface
public interface IHaveLinks
{
[IgnoreDataMember]
IEnumerable<Link> Links { get; }
}
public class Link
{
public string Name { get; set; }
public IReturn Request { get; set; }
public string Method { get; set; }
}
Then I use a response filter to generate the urls and populate the response headers with the links.
this.ResponseFilters.Add((req, res, dto) =>
{
if (!(dto is IHaveLinks))
return;
var links = (dto as IHaveLinks).Links
if(links == null || !links.Any())
return;
var linksText = links
.Select(x => string.Format("<{0}>; rel={1}"), x.Request.ToUrl(x.Method), x.Name));
var linkHeader = string.Join(", ", linksText);
res.AddHeader("Link", linkHeader);
});
This seems the cleanest way. The Link object above effectively says "If you make this request with this method you will get back the named resource". The only HTTP thing that bleeds up to the BLL is Method. But you could get rid of that and only pass back GET urls. Or map it to some generalised "operation"?
As an example:
public class ExampleService : Service
{
public ExamplesResponse Get(ExamplesRequest request)
{
var page = request.Page;
var data = // get data;
return new ExamplesResponse
{
Examples = data,
Links = new []
{
new Link { Name = "next", Request = request.AddPage(1), Method = "GET" },
new Link { Name = "previous", Request = request.AddPage(-1), Method = "GET" },
}
}
}
}
[Route("/examples/{Page}")]
public class ExamplesRequest : IReturn<ExamplesResponse>
{
public int Page { get; set; }
// ...
}
(The AddPage method returns a clone of the request and sets the Page property appropriately.)
Hope that helps.

Categories

Resources