ODATA WebService, get $metadata C#

2019-05-28 14:57发布

问题:

I have a WebService that expose this $metadata:

<?xml version="1.0" encoding="UTF-8" standalone="true"?>
<edmx:Edmx xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" Version="1.0">     > <edmx:DataServices m:DataServiceVersion="1.0"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">

<Schema xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns="http://schemas.microsoft.com/ado/2007/05/edm"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
Namespace="NAV"> 
<EntityType Name="PAsset"> 
<Key> <PropertyRef Name="No"/> </Key>  <Property Name="No" Nullable="false" Type="Edm.String"/>  <Property Name="Description"
Nullable="true" Type="Edm.String"/>  <Property Name="Inactive"
Nullable="false" Type="Edm.Boolean"/>  <Property Name="Insured"
Nullable="false" Type="Edm.Boolean"/> </EntityType> 

<EntityType Name="PAssetsDepreciationBook">

<EntityType Name="PBankAPostGrp">
<EntityType Name="PCompany">
<EntityType Name="PCustomer">

Is possible get the information in this $metadata in a C# aplication?

I Have a Application with a reference to the Service working, and some code i'm using:

uriString = String.Format("PAssetsDepreciationBook?$filter=FA_No eq '{0}'",cliente);

contex = new ServiceReference1.NAV(new Uri(serviceEndPoint)); contex.Credentials = CredentialCache.DefaultCredentials;

var customers = contex.Execute(new Uri(uriString, UriKind.Relative));

foreach (var c in customers) { result = result + c.Acquisition_Cost; } return result;

This works fine, but to get $metadata doesn't.

回答1:

There is already code (mostly) to do this. It involves the ODataMessageReader.ReadMetadataDocument() call:

var request = WebRequest.CreateHttp(baseUrl + "$metadata");
var metadataMessage =
    new ClientHttpResponseMessage((HttpWebResponse)request.GetResponse());
using (var messageReader = new ODataMessageReader(metadataMessage))
{
    IEdmModel edmModel = messageReader.ReadMetadataDocument();
    // Do stuff with edmModel here
}

You need the ClientHttpResponseMessage class but that is simple (from ODataLib101):

public class ClientHttpResponseMessage : IODataResponseMessage
{
    private readonly HttpWebResponse webResponse;

    public ClientHttpResponseMessage(HttpWebResponse webResponse)
    {
        if (webResponse == null)
            throw new ArgumentNullException("webResponse");
        this.webResponse = webResponse;
    }

    public IEnumerable<KeyValuePair<string, string>> Headers
    {
        get
        {
            return this.webResponse.Headers.AllKeys.Select(
                headerName => new KeyValuePair<string, string>(
                   headerName, this.webResponse.Headers.Get(headerName)));
        }
    }

    public int StatusCode
    {
        get { return (int)this.webResponse.StatusCode; }
        set
        {
            throw new InvalidOperationException(
                "The HTTP response is read-only, status code can't be modified on it.");
        }
    }

    public Stream GetStream()
    {
        return this.webResponse.GetResponseStream();
    }

    public string GetHeader(string headerName)
    {
        if (headerName == null)
            throw new ArgumentNullException("headerName");
        return this.webResponse.Headers.Get(headerName);
    }

    public void SetHeader(string headerName, string headerValue)
    {
        throw new InvalidOperationException(
            "The HTTP response is read-only, headers can't be modified on it.");
    }
}

With the IEdmModel, you can access all the information in the metadata to do things like build a dictionary of type->controller name:

Dictionary<type, string> typeControllerMap =
    edmModel.SchemaElements.OfType<IEdmEntityContainer>()
            .Single()
            .Elements.OfType<IEdmEntitySet>()
            .Select(es => new { t = FindType(es.ElementType.FullName()), n = es.Name })
            .Where(tn => tn.t != null)
            .ToDictionary(tn => tn.t, tn => tn.n);

The FindType() call in the middle of the LINQ chain above simply searches all assemblies for a type given the type name:

private static Type FindType(string fullName)
{
    return
        AppDomain.CurrentDomain.GetAssemblies()
            .Where(a => !a.IsDynamic)
            .SelectMany(a => a.GetTypes())
            .FirstOrDefault(t => t.FullName.Equals(fullName));
}


回答2:

You can request the metadata as a plain XML using HttpWebRequest for example. If you need to parse it, you can use EdmLib (Microsoft.Data.Edm.dll) which can be found on NuGet or even better ODataLib (Microsoft.Data.OData.dll) also on Nuget which has ODataMessageReader.ReadMetadataDocument specifically designed to read these (it still returns the EDM object model for it, it just deals with the EDMX wrapper and versioning for you).



回答3:

Thanks, it work just fine.

Just like this:

// Create a request for the URL. 
WebRequest request = WebRequest.Create("http://localhost:7048/DynamicsNAV70/OData/$metadata");

//set the credentials.
request.Credentials = CredentialCache.DefaultCredentials;

// Get the response.
WebResponse response = request.GetResponse();

// Get the stream containing content returned by the server.
Stream dataStream = response.GetResponseStream();

// Open the stream using a StreamReader for easy access.
StreamReader reader = new StreamReader(dataStream);

// Read the content.
string responseFromServer = reader.ReadToEnd();

XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(new StringReader(responseFromServer));

// Clean up the streams and the response.
reader.Close();
response.Close();

After this, just parse the data like a xml file.