I am trying to create events using MS Graph. We are using json objects with the call as in this example
using (HttpClient httpClient = new HttpClient())
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", msBearerToken);
var callJson = new
{
Subject = EventSummery.Title,
Body = new
{
ContentType = BodyType.Html.ToString(),
Content = EventSummery.Description
},
Start = new
{
DateTime = EventSummery.StartDateUTC.ToString("yyyy-MM-ddTHH:mm:ss"),
TimeZone = "GMT Standard Time"
},
End = new
{
DateTime = EventSummery.EndDateUTC.ToString("yyyy-MM-ddTHH:mm:ss"),
TimeZone = "GMT Standard Time"
}
};
I need to add an open extension however the documentation I need to add this attribute
"extensions": [
{
"#odata.type": "microsoft.graph.openTypeExtension",
"extensionName": "Com.Contoso.Referral",
"companyName": "Wingtip Toys",
"expirationDate": "2015-12-30T11:00:00.000Z",
"dealValue": 10000
}]
however #odata.type throws an error if I put it in this form:
Extensions = new
{
"#odata.type": "microsoft.graph.openTypeExtension",
"extensionName": "Com.Contoso.Referral",
"companyName": "Wingtip Toys",
"expirationDate": "2015-12-30T11:00:00.000Z",
"dealValue": 10000
}
What am I missing how can I make this call successfully?
I'm afraid that you cannot create anonymous object with property that has a special character at the beginning.
Only way is to create a class with properties that have JsonPropertyName attribute and then use json serializer.
public class OpenTypeExtension
{
[JsonPropertyName("#odata.type")]
public string ODataType { get; set; }
[JsonPropertyName("extensionName")]
public string ExtensionName { get; set; }
[JsonPropertyName("companyName")]
public string CompanyName { get; set; }
[JsonPropertyName("expirationDate")]
public string ExpirationDate{ get; set; }
[JsonPropertyName("dealValue")]
public string DealValue{ get; set; }
public OpenTypeExtension()
{
ODataType = "microsoft.graph.openTypeExtension";
}
}
Related
I have a rather simple(for now) Odata controller that does not work in the way I expect.
Backend store is a Mongo database, but I don't think that is relevant.
Model class:
public class EventModel
{
[Key]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string EventName { get; set; }
public DateTime EventTime { get; set; }
// Fields omitted to shorten sample
public IDictionary<string,object?> Data { get; set; }
}
OData controller is added like so:
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Repository.Model.EventModel>("Events");
services.AddControllers()
.AddOData(options => options
.Select()
.Filter()
.OrderBy()
.Expand()
.Count()
.SetMaxTop(null)
.AddRouteComponents("eventservice/odata", builder.GetEdmModel()));
And this is the controller:
public class EventODataController : ODataController
{
private readonly IEventRepository _repository;
public EventODataController(IEventRepository repository)
{
_repository = repository;
}
[HttpGet("eventservice/odata/Events")]
[EnableQuery]
public ActionResult Get()
{
var data = _repository.GetAsQueryable();
return Ok(data);
}
Expected result when doing a HTTP get to eventservice/odata/Events$top=100 should be along the lines of this:
{
"#odata.context": "http://127.0.0.1:7125/eventservice/odata/$metadata#Events",
"#odata.count": 1080302,
"value": [
{
"Id": "63dbbcc9920829279f559025",
"EventName": "Asset",
"EventTime": "2022-09-27T09:14:15.398+02:00",
// Omitted field data here
"Data": {
"Data1":"Foo",
"Data2":"Bar",
"SomeMore":4
}
}
But it turns out that OData somehow flattens/inlines the dictionary, so the result is this:
"#odata.context": "http://127.0.0.1:7125/eventservice/odata/$metadata#Events",
"#odata.count": 1080302,
"value": [
{
"Id": "63dbbcc9920829279f559025",
"EventName": "Asset",
"EventTime": "2022-09-27T09:14:15.398+02:00",
"AssetId#odata.type": "#Int64",
"AssetId": 1258,
"UniqueCode": "9404120007",
"DbId#odata.type": "#Int64",
"DbId": 1038118,
"SomeData": "ABC",
"MoreData": "108",
"AreaName": "Area51,
...
},
Simple question: How do I stop OData from behaving like this, and do what I expect?
The Application:
.Net Standard Class Library (Containing a series of Repositories)
Latest version https://www.nuget.org/packages/Microsoft.Azure.DocumentDB.Core
Azure Cosmos DB Emulator
Sample Document Structure:
{
"ProfileName": "User Profile 123",
"Country": "UK",
"Tags": [{
"Id": "686e4c9c-f1ab-40ce-8472-cc5d63597263",
"Name": "Tag 1"
},
{
"Id": "caa2c2a0-cc5b-42e3-9943-dcda776bdc20",
"Name": "Tag 2"
}],
"Topics": [{
"Id": "baa2c2a0-cc5b-42e3-9943-dcda776bdc20",
"Name": "Topic A"
},
{
"Id": "aaa2c2a0-cc5b-42e3-9943-dcda776bdc30",
"Name": "Topic B"
},
{
"Id": "eaa2c2a0-cc5b-42e3-9943-dcda776bdc40",
"Name": "Topic C"
}]
}
The Problem:
The issue I have is that any LINQ query I execute that contains a .Select, returns an error stating that the Select method is not supported.
What I Need?
I want to be able to use a LINQ Expression to return all documents WHERE:
Country = UK
Tags contains a specific GUID
Topics contain a specific GUID
What I Need? I want to be able to use a LINQ Expression to return all documents
In your case, the Tags and Topic are object array, if you use the following linq code it will get null.
var q = from d in client.CreateDocumentQuery<Profile>(
UriFactory.CreateDocumentCollectionUri("database", "coll"))
where d.Country == "UK" && d.Tags.Contains(new Tag
{
Id = "686e4c9c-f1ab-40ce-8472-cc5d63597264"
}) && d.Topics.Contains(new Topic
{
Id = "baa2c2a0-cc5b-42e3-9943-dcda776bdc22"
})
select d;
I also try to override IEqualityComparer for Tag and Topic
public class CompareTag: IEqualityComparer<Tag>
{
public bool Equals(Tag x, Tag y)
{
return x != null && x.Id.Equals(y?.Id);
}
public int GetHashCode(Tag obj)
{
throw new NotImplementedException();
}
}
And try it again then get Contains is not supported by Azure documentDb SDK
var q = from d in client.CreateDocumentQuery<Profile>(
UriFactory.CreateDocumentCollectionUri("database", "coll"))
where d.Country == "UK" && d.Tags.Contains(new Tag
{
Id = "686e4c9c-f1ab-40ce-8472-cc5d63597264"
},new CompareTag()) && d.Topics.Contains(new Topic
{
Id = "baa2c2a0-cc5b-42e3-9943-dcda776bdc22"
}, new CompareTopic())
select d;
My workaround is that we could use the SQL query directly. It works correctly on my side. We also could test it on the Azure portal.
SELECT * FROM root WHERE (ARRAY_CONTAINS(root.Tags, {"Id": "686e4c9c-f1ab-40ce-8472-cc5d63597263"}, true) And ARRAY_CONTAINS(root.Topics, {"Id": "baa2c2a0-cc5b-42e3-9943-dcda776bdc20"}, true) And root.Country = 'UK' )
Query with SDK
FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1 };
var endpointUrl = "https://cosmosaccountName.documents.azure.com:443/";
var primaryKey = "VNMIT4ydeC.....";
var client = new DocumentClient(new Uri(endpointUrl), primaryKey);
var sql = "SELECT * FROM root WHERE (ARRAY_CONTAINS(root.Tags, {\"Id\":\"686e4c9c-f1ab-40ce-8472-cc5d63597263\"},true) AND ARRAY_CONTAINS(root.Topics, {\"Id\":\"baa2c2a0-cc5b-42e3-9943-dcda776bdc20\"},true) AND root.Country = \"UK\")";
var profileQuery = client.CreateDocumentQuery<Profile>(
UriFactory.CreateDocumentCollectionUri("dbname", "collectionName"),sql, queryOptions).AsDocumentQuery();
var profileList = new List<Profile>();
while (profileQuery.HasMoreResults)
{
profileList.AddRange(profileQuery.ExecuteNextAsync<Profile>().Result);
}
Profile.cs file
public class Profile
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
public string Country { get; set; }
public string ProfileName { get; set; }
public Tag[] Tags{ get; set; }
public Topic[] Topics { get; set; }
}
Topic.cs
public class Topic
{
public string Id { get; set; }
public string Name { get; set; }
}
Tag.cs
public class Tag
{
public string Id { get; set; }
public string Name { get; set; }
}
So I discovered an interesting problem.
I have a model like this:
public class ApplicantModel
{
[Display(Name = "Firstname", ResourceType = typeof(Resources))]
[MaxLength(50, ErrorMessageResourceName = "FirstName", ErrorMessageResourceType = typeof(Validations), ErrorMessage = null)]
[Required(ErrorMessageResourceName = "FirstName", ErrorMessageResourceType = typeof(Validations), ErrorMessage = null)]
public string Firstname { get; set; }
[Display(Name = "Surname", ResourceType = typeof(Resources))]
[MaxLength(50, ErrorMessageResourceName = "Surname", ErrorMessageResourceType = typeof(Validations), ErrorMessage = null)]
[Required(ErrorMessageResourceName = "Surname", ErrorMessageResourceType = typeof(Validations), ErrorMessage = null)]
public string Surname { get; set; }
}
that is all fine, and when I check the Model state and there is an error on a model I get something like this:
errors:
[{
Key = FirstApplicant.Firstname
Value = ["First name is required field"]
},
{
Key = FirstApplicant.Surname
Value = ["Surname name is required field"]
}].
That is also fine.
Edit:
This is the c# ModelState object visualized as JSON object. Real object looks like this:
ModelState
{System.Web.Mvc.ModelStateDictionary}
Count: 2
IsReadOnly: false
IsValid: false
Keys: Count = 2
Values: Count = 2
Results View: Expanding the Results View will enumerate the IEnumerable
However my question is. Is it possible to somehow change the key? I know that the key is created as the name of object and then the name property on that object.
So it makes sense, but is there any way how to change this default behavior? Or do I have to change the names of objects?
Edit2:
What I am trying to achieve here is that I have a c# ViewModel and knockout ViewModel. and when you do server side validations you get this dictionary of keys and values which I serialize and send to client.
And then I call this function on it on client:
var errors = #Html.Raw(Json.Encode(Model.Errors));
function showErrors(serializedErrors) {
var errors = JSON.parse(serializedErrors);
for (var i = 0; i < errors.length; i++) {
var error = errors[i];
var key = error.Key;
var property = eval("masterModel." + key);
property.setError(error.Value.ErrorMessage);
property.isModified(true);
}
}
showErrors(errors);
And this would work fine if the view model property names match on the server and on client. But for example on server side I have a FirstApplicant.FirstName and on a client side it is ApplicantOne.firstname. Thank you all for help and comments. I hope I explained my problem in more detail this time.
in the end I found a solution to this problem. It is a bit complicated but it works.
First I've created an attribute.
public class ClientNameAttribute : Attribute, IMetadataAware
{
public ClientNameAttribute(string name)
{
this.Name = name;
}
public string Name { get; set; }
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.AdditionalValues["ClientName"] = this.Name;
}
}
Notice that this attribute also implements IMetadataAware
Next step was to create Html helper, so I could call this in a view.
public static class HtmlHelperExtensions
{
public static string CustomModelState<T>(this HtmlHelper<T> helper)
{
var errors = helper.ViewData.ModelState.Select(
m => new { Key = GenerateClientName(m.Key, helper), Value = m.Value.Errors.FirstOrDefault() }).Where(e=> e.Value != null);
return Json.Encode(errors);
}
private static string GenerateClientName<T>(string key, HtmlHelper<T> helper)
{
StringBuilder builder = new StringBuilder();
int periodIndex = -1;
do
{
periodIndex = key.IndexOf('.', periodIndex + 1);
string part = key.Substring(0, periodIndex==-1 ? key.Length : periodIndex);
var partMetadata = ModelMetadata.FromStringExpression(part, helper.ViewData);
object clientName;
if (builder.Length > 0)
{
builder.Append('.');
}
if (partMetadata.AdditionalValues.TryGetValue("ClientName", out clientName))
{
builder.Append(clientName);
}
else
{
builder.Append(partMetadata.PropertyName);
}
}
while (periodIndex != -1);
return builder.ToString();
}
}
CustomModelState is a method that I call in a view.
like this:
var errors = #Html.Raw(Html.CustomModelState());
if (errors.length > 0) {
showErrors("masterModel",errors);
}
this will give you nicely formated errors, with your custom names of properties.
And here are tests for it:
public class TestModel
{
[Required]
public string Normal { get; set; }
[ClientName("Other")]
[Required]
public string Changed { get; set; }
[ClientName("Complicated")]
public TestModelTwo TestModelTwo { get; set; }
}
public class TestModelTwo
{
public string PropertyOne { get; set; }
[ClientName("Two")]
public string PropertyTwo{ get; set; }
}
[TestClass]
public class HtmlHelperExtensionsTests
{
[TestMethod]
public void CustomModelStateTests()
{
var model = new TestModel();
var page = new ViewPage();
page.ViewData.Model = model;
page.ViewData.ModelState.AddModelError("Normal", "Error1");
page.ViewData.ModelState.AddModelError("Changed", "Error2");
HtmlHelper<TestModel> helper = new HtmlHelper<TestModel>(new ViewContext(), page);
var custom = helper.CustomModelState();
string expectedResult =
"[{\"Key\":\"Normal\",\"Value\":{\"Exception\":null,\"ErrorMessage\":\"Error1\"}},{\"Key\":\"Other\",\"Value\":{\"Exception\":null,\"ErrorMessage\":\"Error2\"}}]";
Assert.AreEqual(expectedResult, custom);
}
[TestMethod]
public void CustomModelStateTests_ObjectProperty_With_ClientName()
{
var model = new TestModel();
model.TestModelTwo = new TestModelTwo();
var page = new ViewPage();
page.ViewData.Model = model;
page.ViewData.ModelState.AddModelError("TestModelTwo.PropertyOne", "Error1");
page.ViewData.ModelState.AddModelError("TestModelTwo.PropertyTwo", "Error2");
HtmlHelper<TestModel> helper = new HtmlHelper<TestModel>(new ViewContext(), page);
var custom = helper.CustomModelState();
string expectedResult =
"[{\"Key\":\"Complicated.PropertyOne\",\"Value\":{\"Exception\":null,\"ErrorMessage\":\"Error1\"}},{\"Key\":\"Complicated.Two\",\"Value\":{\"Exception\":null,\"ErrorMessage\":\"Error2\"}}]";
Assert.AreEqual(expectedResult, custom);
}
}
I have hit a problem building a uCommerce site based on top of the demo razor store available http://thesitedoctor.co.uk/portfolio/avenue-clothingcom/
The demo uses servicestack and the ucommerceapi for its basket functions.
I am trying to add a dynamic property to the basket (on an order line) at the point where the user clicks buy. I traced through the productpage.js file and amended the code to add a new property ('message'):
function (data) {
var variant = data.Variant;
$.uCommerce.addToBasket(
{
sku: variant.Sku,
variantSku: variant.VariantSku,
quantity: qty,
message: $('#personalisedMessage').val()
},
function () {
updateCartTotals(addToCartButton);
}
);
});
using firebug, i checked the data that is being posted
addToExistingLine: true
message: "this is a message"
quantity:"1"
sku: "Product (options: none)"
variantSku:""
Posting this does not cause an error, but I cannot tell if it has worked either - I cannot find it in the database, assuming that it would be stored in OrderProperty table. In this scenario, I am 'buying' a product with no variations.
Any help is greatly appreciated with this.
Out of the box you can't add order/line item properties via the API like that. The API payload that you've added to is specified although valid JSON won't get interpreted/used by the API.
Instead what you'll need to do is add your own method to the API. To do this you'll need to implement a service from IUCommerceApiService and then you can do what you need. I've created an example (untested) below and will get it added to the demo store as I think it's a useful bit of functionality to have.
public class AddOrderLineProperty
{
public int? OrderLineId { get; set; }
public string Sku { get; set; }
public string VariantSku { get; set; }
public string Key { get; set; }
public string Value { get; set; }
}
public class AddOrderLinePropertyResponse : IHasResponseStatus
{
public AddOrderLinePropertyResponse() { }
public AddOrderLinePropertyResponse(UCommerce.EntitiesV2.OrderLine line)
{
if (line == null)
{
UpdatedLine = new LineItem();
return;
}
var currency = SiteContext.Current.CatalogContext.CurrentCatalog.PriceGroup.Currency;
var lineTotal = new Money(line.Total.Value, currency);
UpdatedLine = new LineItem()
{
OrderLineId = line.OrderLineId,
Quantity = line.Quantity,
Sku = line.Sku,
VariantSku = line.VariantSku,
Price = line.Price,
ProductName = line.ProductName,
Total = line.Total,
FormattedTotal = lineTotal.ToString(),
UnitDiscount = line.UnitDiscount,
VAT = line.VAT,
VATRate = line.VATRate
};
}
public ResponseStatus ResponseStatus { get; set; }
public LineItem UpdatedLine { get; set; }
}
public class AddOrderLinePropertyService : ServiceBase<AddOrderLineProperty>, IUCommerceApiService
{
protected override object Run(AddOrderLineProperty request)
{
var orderLineId = request.OrderLineId;
var sku = request.Sku;
var variantSku = request.VariantSku;
var orderLine = findOrderLine(orderLineId, sku, variantSku);
addPropertyToOrderLine(orderLine, request.Key, request.Value);
TransactionLibrary.ExecuteBasketPipeline();
var newLine = findOrderLine(orderLineId, sku, variantSku);
return new AddOrderLinePropertyResponse(newLine);
}
private void addPropertyToOrderLine(OrderLine orderLine, string key, string value)
{
if (orderLine == null)
return;
orderLine[key] = value;
orderLine.Save();
}
private static OrderLine findOrderLine(int? orderLineId, string sku, string variantSku)
{
return orderLineId.HasValue
? getOrderLineByOrderLineId(orderLineId)
: getOrderLineBySku(sku, variantSku);
}
private static OrderLine getOrderLineBySku(string sku, string variantSku)
{
return String.IsNullOrWhiteSpace(variantSku)
? getOrderLines().FirstOrDefault(l => (l.Sku == sku))
: getOrderLines().FirstOrDefault(l => (l.Sku == sku && l.VariantSku == variantSku));
}
private static OrderLine getOrderLineByOrderLineId(int? orderLineId)
{
return getOrderLines().FirstOrDefault(l => l.OrderLineId == orderLineId);
}
private static ICollection<OrderLine> getOrderLines()
{
return TransactionLibrary.GetBasket().PurchaseOrder.OrderLines;
}
}
You'll need to add the new method to uCommerce.jQuery.js as well something like this:
addOrderLineProperty: function (options, onSuccess, onError) {
var defaults = {
orderLineId: 0
};
var extendedOptions = $.extend(defaults, options);
callServiceStack({ AddOrderLineProperty: extendedOptions }, onSuccess, onError);
}
Let me know if you have any issues using it.
Tim
DB MetersTree TABLE
id text parentId state
0 root 0 open
1 level 1 1 open
2 level 1 1 open
...
CONTROLLER
public ActionResult GetDemoTree()
{
OsosPlus2DbEntities entity = new OsosPlus2DbEntities();
MetersTree meterTree = entity.MetersTree.FirstOrDefault();
return Json(meterTree, JsonRequestBehavior.AllowGet);
}
DATA FORMAT THAT SHOULD BE (for example)
[{
"id": 1,
"text": "Node 1",
"state": "closed",
"children": [{
"id": 11,
"text": "Node 11"
},{
"id": 12,
"text": "Node 12"
}]
},{
"id": 2,
"text": "Node 2",
"state": "closed"
}]
How can I create tree Json Data? If I write MetersTree with its relationships I get the error that is defined in the title.
You need to break the circular reference that is being picked up because of the navigational property in your EF class.
You can map the results into an anonymous type like this, although this is untested:
public ActionResult GetDemoTree()
{
OsosPlus2DbEntities entity = new OsosPlus2DbEntities();
MetersTree meterTree = entity.MetersTree.FirstOrDefault();
var result = from x in meterTree
select new
{
x.id,
x.text,
x.state,
children = x.children.Select({
c => new {
c.id,
c.text
})
};
return Json(result, JsonRequestBehavior.AllowGet);
}
I solved it like this:
VIEW MODEL
public class MetersTreeViewModel
{
public int id { get; set; }
public string text { get; set; }
public string state { get; set; }
public bool #checked { get; set; }
public string attributes { get; set; }
public List<MetersTreeViewModel> children { get; set; }
}
CONTROLLER
public ActionResult GetMetersTree()
{
MetersTree meterTreeFromDb = entity.MetersTree.SingleOrDefault(x => x.sno == 5); //in my db this is the root.
List<MetersTreeViewModel> metersTreeToView = buildTree(meterTreeFromDb.Children).ToList();
return Json(metersTreeToView, JsonRequestBehavior.AllowGet);
}
BuildTree Method
private List<MetersTreeViewModel> BuildTree(IEnumerable<MetersTree> treeFromDb)
{
List<MetersTreeViewModel> metersTreeNodes = new List<MetersTreeViewModel>();
foreach (var node in treeFromDb)
{
if (node.Children.Any())
{
metersTreeNodes.Add(new MetersTreeViewModel
{
id = node.sno,
text = node.Text,
state = node.Text,
children = BuildTree(node.Children)
});
}
else {
metersTreeNodes.Add(new MetersTreeViewModel
{
id = node.sno,
text = node.Text,
state = node.Text
});
}
}
return metersTreeNodes;
}
Thanks to all who are interested in ...