I'm integrating protobuf-net into our WCF services based solution but have ran into a snag that I can't figure out. The following class will serialize fine, all except for the ObjectId property.
/// <summary>
/// A service data object that represents a user of the system.
/// </summary>
[DataContract(Namespace = "http://LINKS.Service.Security.Administration", Name = "User")]
public sealed class UserMessagePart : IMessagePart
{
private ObjectId objectId;
private string userName;
private string firstName;
private string lastName;
private string middleName;
private string gender;
private string emailAddress;
private bool isAccountDisabled;
private string disabledMeaning;
private DateTime createDtTm;
private DateTime disabledDtTm;
private VersionMessagePart version;
/// <summary>
/// Main constructor.
/// </summary>
public UserMessagePart(ObjectId objectId, string userName, string firstName, string lastName, string middleName,
string gender, string emailAddress, bool isAccountDisabled, string disabledMeaning, DateTime createDtTm,
DateTime disabledDtTm, VersionMessagePart version)
{
this.objectId = objectId;
this.userName = userName;
this.firstName = firstName;
this.lastName = lastName;
this.middleName = middleName;
this.gender = gender;
this.emailAddress = emailAddress;
this.isAccountDisabled = isAccountDisabled;
this.disabledMeaning = disabledMeaning;
this.createDtTm = createDtTm;
this.disabledDtTm = disabledDtTm;
this.version = version;
}
/// <summary>
/// Parameterless constructor.
/// </summary>
public UserMessagePart(){}
/// <summary>
/// The unique identifier for this user.
/// </summary>
[DataMemberAttribute(IsRequired = true, Name = "ObjectId", Order = 0)]
public ObjectId ObjectId
{
get { return objectId; }
set { objectId = value; }
}
/// <summary>
/// The user's login identity.
/// </summary>
[DataMemberAttribute(IsRequired = true, Name = "UserName", Order = 1)]
public string UserName
{
get { return userName; }
set { userName = value; }
}
// ...abbreviated code for readability...
/// <summary>
/// Version information for this user
/// </summary>
[DataMemberAttribute(IsRequired = true, Name = "Version", Order = 11)]
public VersionMessagePart Version
{
get { return version; }
set { version = value; }
}
}
Here's the class that's toying with me:
/// <summary>
/// Uniquely identifies any <see cref="IMessagePart"/> implementation in the system.
/// </summary>
[DataContract(Namespace = "http://LINKS.Service.Core", Name = "ObjectIdentifier")]
public class ObjectId
{
private string id;
private string domain;
private string modelName;
private long instanceId;
private int versionNumber;
/// <summary>
/// Default constructor. (required for Protobuf-net)
/// </summary>
public ObjectId()
{
}
/// <summary>
/// Main constructor.
/// </summary>
public ObjectId(string domain, string modelName, long instanceId, int versionNumber)
{
id = string.Format("{0}#{1}#{2}#{3}", domain, modelName, instanceId, versionNumber);
this.domain = domain;
this.modelName = modelName;
this.instanceId = instanceId;
this.versionNumber = versionNumber;
}
/// <summary>
/// The unique identifier for the <see cref="ObjectId"/>. The format of this string is not
/// guaranteed and should only be treated as a unique key. No attempts to parse it should be
/// made by client applications.
/// </summary>
[DataMemberAttribute(IsRequired = true, Name = "Id", Order = 0)]
public string Id
{
get { return id; }
set
{
id = value;
string[] parts = id.Split('#');
domain = parts[0];
modelName = parts[1];
instanceId = long.Parse(parts[2]);
versionNumber = int.Parse(parts[3]);
}
}
/// <summary>
/// The system domain that the <see cref="ObjectId"/> originated from.
/// </summary>
public string Domain
{
get { return domain; }
}
/// <summary>
/// The type of object that this <see cref="ObjectId"/> refers to.
/// </summary>
public string ModelName
{
get { return modelName; }
}
/// <summary>
/// The object instance identifier for the object that this <see cref="ObjectId"/>
/// refers to. Typically, this is a database key.
/// </summary>
public long InstanceId
{
get { return instanceId; }
}
/// <summary>
/// The version instance of the object referred to by this <see cref="ObjectId"/>
/// </summary>
public int VersionNumber
{
get { return versionNumber; }
}
/// <summary>
/// Returns a string representation of the object identifier.
/// </summary>
new public string ToString()
{
return id;
}
}
I've tried multiple things with no luck. Any thoughts would be greatly appreciated!
(I'm the author of protobuf-net)
I'm about to leave, so I can't verify "right now", but at a first guess, I'd say that the Order = 0 looks a likely culprit. Try Order = 1 or some other number ("protocol buffers" identifiers must be positive).
Note that you'll need to tweak this both on ObjectId and on Id.
I've taken a copy of the above, and I'll check on the train...
Note also that the WCF hooks work best if you are using assembly sharing - i.e. you have the same class at the client and server. If you are using svcutl (or "Add Service Reference"), it sometimes remaps the numbers; there is a trick to fix this - let me know if this is an issue (basically, double check the Order on the properties generated by the tooling)
Related
Good morning. I'm trying to cut down the time on a LINQ Query. During the execution of the block of code, against large datasets it takes about 30-40 seconds to complete, which is way too long.
foreach (var patientVisitId in patientVisitIds)
{
var firstVisit = visitsWithBills.First(vb => vb.Visit.PatientVisitId == patientVisitId).Visit;
firstVisit.Bills = (from visitBill in visitsWithBills
where visitBill.Visit.PatientVisitId == patientVisitId
select visitBill.Bill).ToList();
visitTOs.Add(firstVisit);
}
I've tried replacing the == within the where statement with .contains which I read is supposed to be quicker, that almost doubles the execution time.
foreach (var patientVisitId in patientVisitIds)
{
var firstVisit = visitsWithBills.First(vb => vb.Visit.PatientVisitId == patientVisitId).Visit;
firstVisit.Bills = (from visitBill in visitsWithBills
where visitBill.Visit.PatientVisitId.Contains(patientVisitId)
select visitBill.Bill).ToList();
visitTOs.Add(firstVisit);
}
Here's the Object that firstVisit represents.
public class VisitTO
{
#region { Instance properties }
/// <summary>
/// Gets or sets the bed/room number for the visit
/// </summary>
public string Bed { get; set; }
/// <summary>
/// Gets or sets the bills for the visit
/// </summary>
public List<BillTO> Bills { get; set; }
/// <summary>
/// Gets or sets the date of admission for the visit
/// </summary>
public DateTime DateOfAdmission { get; set; }
/// <summary>
/// Gets or sets the primary diagnosis for the patient
/// </summary>
public string DX1 { get; set; }
/// <summary>
/// Gets or sets the secondary diagnosis for the patient
/// </summary>
public string DX2 { get; set; }
/// <summary>
/// Gets or sets the tertiary diagnosis for the patient
/// </summary>
public string DX3 { get; set; }
/// <summary>
/// Gets or sets the quaternary diagnosis for the patient
/// </summary>
public string DX4 { get; set; }
/// <summary>
/// Gets or sets the quinary diagnosis for the patient
/// </summary>
public string DX5 { get; set; }
/// <summary>
/// Gets or sets the senary diagnosis for the patient
/// </summary>
public string DX6 { get; set; }
/// <summary>
/// Gets or sets whether the patient has been discharged
/// </summary>
public bool IsDischarged { get; set; }
/// <summary>
/// Gets or sets the patient's full name
/// </summary>
public string PatientName { get; set; }
/// <summary>
/// Gets or sets the patient's current visit ID
/// </summary>
public string PatientVisitId { get; set; }
/// <summary>
/// Gets or sets the patient's current visit ID
/// </summary>
public string PatientId { get; set; }
/// <summary>
/// Gets or sets the name of the patient's primary care physician
/// </summary>
public string PrimaryCarePhysician { get; set; }
/// <summary>
/// Gets or sets the hosting site
/// </summary>
public string Site { get; set; }
/// <summary>
/// Gets or sets the team assignment
/// </summary>
public string Team { get; set; }
#endregion { Instance properties }
}
Here's BillTO object.
public class BillTO
{
#region { Public instance properties }
/// <summary>
/// Gets or sets the bill's date
/// </summary>
public DateTime Date { get; set; }
/// <summary>
/// Gets or sets the name for the doctor on the bill
/// </summary>
public string DoctorName { get; set; }
/// <summary>
/// Gets or sets the bill's type
/// </summary>
public string Type { get; set; }
/// <summary>
/// Gets or sets the encounter for this bill
/// </summary>
public string Encounter { get; set; }
/// <summary>
/// Gets or sets the CPT Code
/// </summary>
public string CptCode { get; set; }
#endregion { Public instance properties }
}
Database Query to the get the list.
private static readonly Func<MDataContext, IQueryable<VisitBillTO>> ActiveVisitsWithBillsQuery =
CompiledQuery.Compile<MContext, IQueryable<VisitBillTO>>(
dbContext => (
from visit in dbContext.AV
join bill in dbContext.ABills on visit.PatientVisitId equals bill.PatientVisitId
where (visit.BFlag == null || visit.BFlag != "BI")
orderby visit.PatientVisitId
select new VisitBillTO
{
Bill = new BillTO
{
Date = bill.Date.GetValueOrDefault(DateTime.Today),
DoctorName = bill.DoctorName,
Type = bill.Type,
Encounter = bill.Encounter,
CptCode = bill.CptCode
},
Visit = new VisitTO
{
Bed = visit.Bed,
DateOfAdmission = visit.DateOfAdmission.GetValueOrDefault(DateTime.Today),
DX1 = visit.DX1,
DX2 = visit.DX2,
DX3 = visit.DX3,
DX4 = visit.DX4,
DX5 = visit.DX5,
DX6 = visit.DX6,
IsDischarged = (visit.IsDischargedCode != null && visit.IsDischargedCode == "Y"),
PatientName = (visit.PatientFullName ?? visit.PatientLastName + ", " + visit.PatientFirstName),
PatientVisitId = visit.PatientVisitId,
PatientId = visit.PatientID,
PrimaryCarePhysician = visit.PrimaryCarePhysician,
Site = visit.Site,
Team = visit.Team
}
}
));
As I expected, you can do this far more efficiently in one query:
from visit in dbContext.AV
where (visit.BFlag == null || visit.BFlag != "BI")
&& patientVisitIds.Contains(visit.PatientVisitId)
orderby visit.PatientVisitId
select new VisitBillTO
{
Bed = visit.Bed,
...
Team = visit.Team,
Bills = (from bill
in visit.Bills
select new BillTO
{
Date = bill.Date.GetValueOrDefault(DateTime.Today),
DoctorName = bill.DoctorName,
Type = bill.Type,
Encounter = bill.Encounter,
CptCode = bill.CptCode
})
}
Now the database does all the heavy lifting of combining the objects. Everything is shaped as you want in one go.
Note that I assume the navigation property visit.Bills to exist. These properties normally exist in LINQ-to-SQL contexts, but in the designer for some reason they're always collapsed by default, so people tend to overlook them. If for some reason the property isn't there you can replace...
Bills = (from bill in visit.Bills
by...
Bills = (dbContext.ABills where visit.PatientVisitId == bill.PatientVisitId
The name '' does not exist in the current context
crn
courseid
timedays
Roomnumber
At the display, none of the items are recognized. Why is the display not seeing them when they were declared as set and gets?
using System;
using System.Collections.Generic;
using System.Text;
public class Section
{
private int crn;
private String courseId;
private String timeDays;
private String roomNumber;
private int instructor;
/// <summary>
/// Creates a new instance of a section
/// </summary>
/// <param name="crn"></param>
/// <param name="courseId"></param>
/// <param name="timeDays"></param>
/// <param name="roomNumber"></param>
/// <param name="instructor"></param>
public Section(int crn, string courseId, string timeDays, string roomNumber, int instructor)
:this(crn, courseId, timeDays, roomNumber, instructor, "")
{
}
/// <summary>
/// Creates a new instance of a section
/// </summary>
/// <param name="crn"></param>
/// <param name="courseId"></param>
/// <param name="timeDays"></param>
/// <param name="roomNumber"></param>
/// <param name="instructor"></param>
/// <param name="message"></param>
public Section(int crn, string courseId, string timeDays, string roomNumber, int instructor, string message)
{
this.Crn = crn;
this.CourseId = courseId;
this.TimeDays = timeDays;
this.RoomNumber = roomNumber;
this.Instructor = instructor;
this.Message = message;
}
/// <summary>
/// Gets or sets the crn
/// </summary>
public int Crn { get; set; }
/// <summary>
/// gets or sets the course id
/// </summary>
public string CourseId { get; set; }
/// <summary>
/// gets or sets the time days
/// </summary>
public string TimeDays { get; set; }
/// <summary>
/// gets or sets the room number
/// </summary>
public string RoomNumber { get; set; }
/// <summary>
/// Gets or sets the instructor
/// </summary>
public int Instructor { get; set; }
/// <summary>
/// Gets or sets the message
/// </summary>
public string Message { get; set; }
public void display(){
System.Console.WriteLine("CRN = "+ getCrn());
System.Console.WriteLine("CourseID = "+ getCourseId());
System.Console.WriteLine("Time Days = " + getTimeDays());
System.Console.WriteLine("Room Number = " + getRoomNumber());
}
}
I think display() needs to look like below. I don't know how your display() function is even compiling.
public void display()
{
System.Console.WriteLine("CRN = "+ Crn);
System.Console.WriteLine("CourseID = "+ CourseId);
System.Console.WriteLine("Time Days = " + TimeDays);
System.Console.WriteLine("Room Number = " + RoomNmber);
}
In addition, since you have all your properties declared with { get; set; }, you don't need any backing member variables (the compiler automatically takes care of these for you). So you can delete the lines that look like this:
private int crn;
private String courseId;
private String timeDays;
private String roomNumber;
private int instructor;
This should work and it's very basic. My json string is (from the debugger):
json "{\"companyId\":0,\"companyName\":\"Windward 3\",\"apiKey\":null,\"isEnabled\":false,\"isActive\":false,\"accruedRtusThisMonth\":0,\"billedRtusThisMonth\":0,\"overageChargesThisMonth\":0.0,\"pricingMode\":3,\"discount\":null,\"billingMode\":1,\"maxAdditionalMonthlyCharge\":123.0,\"billing\":{\"personId\":0,\"companyId\":0,\"isActive\":false,\"isAdmin\":false,\"isBilling\":false,\"firstName\":\"David\",\"lastName\":\"Thielen\",\"address1\":\"1 Main St.\",\"address2\":null,\"city\":\"Boulder\",\"state\":\"CO\",\"country\":\"USA\",\"postalCode\":\"80301\",\"phone\":\"123-456-7890\",\"email\":\"david#windward.net\",\"password\":\"tree\"},\"creditCard\":{\"cardNumber\":\"4111111111111111\",\"expiration\":\"2015-02-18T23:37:01.3135786Z\",\"cvv\":\"123\",\"useCardPerson\":false,\"cardPerson\":null},\"nextBaseBillingDate\":\"0001-01-01T00:00:00\",\"nextOverageBillingDate\":\"0001-01-01T00:00:00\",\"billingStatus\":0,\"billingErrorDate\":null,\"deactivateDate\":null,\"deleteDate\":null}" string
My code is as follows:
CompanyWrapper companyWrapper = JsonConvert.DeserializeObject<CompanyWrapper>(json,
new JsonSerializerSettings());
And the CompanyWrapper class is:
public class CompanyWrapper
{
/// <summary>
/// For the JSON population.
/// </summary>
public CompanyWrapper()
{
}
/// <summary>
/// For unit tests
/// </summary>
public CompanyWrapper(string companyName, PricingPlan.PRICING_MODE pricingMode, Company.BILLING_MODE billingMode, decimal maxAdditionalMonthlyCharge, PersonWrapper billing, CreditCardWrapper creditCard)
{
this.companyName = companyName;
this.pricingMode = pricingMode;
this.billingMode = billingMode;
this.maxAdditionalMonthlyCharge = maxAdditionalMonthlyCharge;
this.billing = billing;
this.creditCard = creditCard;
}
/// <summary>
/// The primary key. This is auto-generated in the database.
/// </summary>
public int companyId { get; private set; }
/// <summary>
/// The company name. This cannot be changed.
/// </summary>
public string companyName { get; private set; }
...
}
On return companyWrapper.companyName == null. That should be assigned. What am I missing?
thanks - dave
You need to make the property setters public.
Postman is a tool that can be used to easily test restful web services.
If an Asp.Net project is using WebApi in conjunction with WebApi Help pages documentation can be automatically generated for exposed restful web services.
This autogenerated documentation is good, but it could be made better via added accessibility.
How can these technologies be combined to generate a JSON file that can be imported in Postman?
Expanding on the blog post "Using ApiExplorer to export API information to PostMan, a Chrome extension for testing Web APIs" it is possible to generate a JSON file that can be imported into Postman for use in testing and documenting.
First you need to setup a a controller that can export JSON
/// <summary>
/// Based on
/// http://blogs.msdn.com/b/yaohuang1/archive/2012/06/15/using-apiexplorer-to-export-api-information-to-postman-a-chrome-extension-for-testing-web-apis.aspx
/// </summary>
[RoutePrefix("api/postman")]
public class PostmanApiController : ApiController
{
/// <summary>
/// Produce [POSTMAN](http://www.getpostman.com) related responses
/// </summary>
public PostmanApiController()
{
// exists for documentation purposes
}
private readonly Regex _pathVariableRegEx = new Regex("\\{([A-Za-z0-9-_]+)\\}", RegexOptions.ECMAScript | RegexOptions.Compiled);
private readonly Regex _urlParameterVariableRegEx = new Regex("=\\{([A-Za-z0-9-_]+)\\}", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Get a postman collection of all visible Api
/// (Get the [POSTMAN](http://www.getpostman.com) chrome extension)
/// </summary>
/// <returns>object describing a POSTMAN collection</returns>
/// <remarks>Get a postman collection of all visible api</remarks>
[HttpGet]
[Route(Name = "GetPostmanCollection")]
[ResponseType(typeof (PostmanCollectionGet))]
public IHttpActionResult GetPostmanCollection()
{
return Ok(this.PostmanCollectionForController());
}
private PostmanCollectionGet PostmanCollectionForController()
{
var requestUri = Request.RequestUri;
var baseUri = requestUri.Scheme + "://" + requestUri.Host + ":" + requestUri.Port
+ HttpContext.Current.Request.ApplicationPath;
var postManCollection = new PostmanCollectionGet
{
Id = Guid.NewGuid(),
Name = "[Name of your API]",
Timestamp = DateTime.Now.Ticks,
Requests = new Collection<PostmanRequestGet>(),
Folders = new Collection<PostmanFolderGet>(),
Synced = false,
Description = "[Description of your API]"
};
var helpPageSampleGenerator = Configuration.GetHelpPageSampleGenerator();
var apiExplorer = Configuration.Services.GetApiExplorer();
var apiDescriptionsByController = apiExplorer.ApiDescriptions.GroupBy(
description =>
description.ActionDescriptor.ActionBinding.ActionDescriptor.ControllerDescriptor.ControllerType);
foreach (var apiDescriptionsByControllerGroup in apiDescriptionsByController)
{
var controllerName = apiDescriptionsByControllerGroup.Key.Name.Replace("Controller", string.Empty);
var postManFolder = new PostmanFolderGet
{
Id = Guid.NewGuid(),
CollectionId = postManCollection.Id,
Name = controllerName,
Description = string.Format("Api Methods for {0}", controllerName),
CollectionName = "api",
Order = new Collection<Guid>()
};
foreach (var apiDescription in apiDescriptionsByControllerGroup
.OrderBy(description => description.HttpMethod, new HttpMethodComparator())
.ThenBy(description => description.RelativePath)
.ThenBy(description => description.Documentation.ToString(CultureInfo.InvariantCulture)))
{
TextSample sampleData = null;
var sampleDictionary = helpPageSampleGenerator.GetSample(apiDescription, SampleDirection.Request);
MediaTypeHeaderValue mediaTypeHeader;
if (MediaTypeHeaderValue.TryParse("application/json", out mediaTypeHeader)
&& sampleDictionary.ContainsKey(mediaTypeHeader))
{
sampleData = sampleDictionary[mediaTypeHeader] as TextSample;
}
// scrub curly braces from url parameter values
var cleanedUrlParameterUrl = this._urlParameterVariableRegEx.Replace(apiDescription.RelativePath, "=$1-value");
// get pat variables from url
var pathVariables = this._pathVariableRegEx.Matches(cleanedUrlParameterUrl)
.Cast<Match>()
.Select(m => m.Value)
.Select(s => s.Substring(1, s.Length - 2))
.ToDictionary(s => s, s => string.Format("{0}-value", s));
// change format of parameters within string to be colon prefixed rather than curly brace wrapped
var postmanReadyUrl = this._pathVariableRegEx.Replace(cleanedUrlParameterUrl, ":$1");
// prefix url with base uri
var url = baseUri.TrimEnd('/') + "/" + postmanReadyUrl;
var request = new PostmanRequestGet
{
CollectionId = postManCollection.Id,
Id = Guid.NewGuid(),
Name = apiDescription.RelativePath,
Description = apiDescription.Documentation,
Url = url,
Method = apiDescription.HttpMethod.Method,
Headers = "Content-Type: application/json",
Data = sampleData == null
? null
: sampleData.Text,
DataMode = "raw",
Time = postManCollection.Timestamp,
Synced = false,
DescriptionFormat = "markdown",
Version = "beta",
Responses = new Collection<string>(),
PathVariables = pathVariables
};
postManFolder.Order.Add(request.Id); // add to the folder
postManCollection.Requests.Add(request);
}
postManCollection.Folders.Add(postManFolder);
}
return postManCollection;
}
}
/// <summary>
/// Quick comparer for ordering http methods for display
/// </summary>
internal class HttpMethodComparator : IComparer<HttpMethod>
{
private readonly string[] _order =
{
"GET",
"POST",
"PUT",
"DELETE"
};
public int Compare(HttpMethod x, HttpMethod y)
{
return Array.IndexOf(this._order, x.ToString()).CompareTo(Array.IndexOf(this._order, y.ToString()));
}
}
and generate the proper models:
One for the PostManCollection
/// <summary>
/// [Postman](http://getpostman.com) collection representation
/// </summary>
public class PostmanCollectionGet
{
/// <summary>
/// Id of collection
/// </summary>
[JsonProperty(PropertyName = "id")]
public Guid Id { get; set; }
/// <summary>
/// Name of collection
/// </summary>
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
/// <summary>
/// Collection generation time
/// </summary>
[JsonProperty(PropertyName = "timestamp")]
public long Timestamp { get; set; }
/// <summary>
/// Requests associated with the collection
/// </summary>
[JsonProperty(PropertyName = "requests")]
public ICollection<PostmanRequestGet> Requests { get; set; }
/// <summary>
/// **unused always false**
/// </summary>
[JsonProperty(PropertyName = "synced")]
public bool Synced { get; set; }
/// <summary>
/// folders within the collection
/// </summary>
[JsonProperty(PropertyName = "folders")]
public ICollection<PostmanFolderGet> Folders { get; set; }
/// <summary>
/// Description of collection
/// </summary>
[JsonProperty(PropertyName = "description")]
public string Description { get; set; }
}
One for the PostmanFolder
/// <summary>
/// Object that describes a [Postman](http://getpostman.com) folder
/// </summary>
public class PostmanFolderGet
{
/// <summary>
/// id of the folder
/// </summary>
[JsonProperty(PropertyName = "id")]
public Guid Id { get; set; }
/// <summary>
/// folder name
/// </summary>
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
/// <summary>
/// folder description
/// </summary>
[JsonProperty(PropertyName = "description")]
public string Description { get; set; }
/// <summary>
/// ordered list of ids of items in folder
/// </summary>
[JsonProperty(PropertyName = "order")]
public ICollection<Guid> Order { get; set; }
/// <summary>
/// Name of the collection
/// </summary>
[JsonProperty(PropertyName = "collection_name")]
public string CollectionName { get; set; }
/// <summary>
/// id of the collection
/// </summary>
[JsonProperty(PropertyName = "collection_id")]
public Guid CollectionId { get; set; }
}
Finally a model for the PostmanRequest
/// <summary>
/// [Postman](http://getpostman.com) request object
/// </summary>
public class PostmanRequestGet
{
/// <summary>
/// id of request
/// </summary>
[JsonProperty(PropertyName = "id")]
public Guid Id { get; set; }
/// <summary>
/// headers associated with the request
/// </summary>
[JsonProperty(PropertyName = "headers")]
public string Headers { get; set; }
/// <summary>
/// url of the request
/// </summary>
[JsonProperty(PropertyName = "url")]
public string Url { get; set; }
/// <summary>
/// path variables of the request
/// </summary>
[JsonProperty(PropertyName = "pathVariables")]
public Dictionary<string, string> PathVariables { get; set; }
/// <summary>
/// method of request
/// </summary>
[JsonProperty(PropertyName = "method")]
public string Method { get; set; }
/// <summary>
/// data to be sent with the request
/// </summary>
[JsonProperty(PropertyName = "data")]
public string Data { get; set; }
/// <summary>
/// data mode of reqeust
/// </summary>
[JsonProperty(PropertyName = "dataMode")]
public string DataMode { get; set; }
/// <summary>
/// name of request
/// </summary>
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
/// <summary>
/// request description
/// </summary>
[JsonProperty(PropertyName = "description")]
public string Description { get; set; }
/// <summary>
/// format of description
/// </summary>
[JsonProperty(PropertyName = "descriptionFormat")]
public string DescriptionFormat { get; set; }
/// <summary>
/// time that this request object was generated
/// </summary>
[JsonProperty(PropertyName = "time")]
public long Time { get; set; }
/// <summary>
/// version of the request object
/// </summary>
[JsonProperty(PropertyName = "version")]
public string Version { get; set; }
/// <summary>
/// request response
/// </summary>
[JsonProperty(PropertyName = "responses")]
public ICollection<string> Responses { get; set; }
/// <summary>
/// the id of the collection that the request object belongs to
/// </summary>
[JsonProperty(PropertyName = "collection-id")]
public Guid CollectionId { get; set; }
/// <summary>
/// Synching
/// </summary>
[JsonProperty(PropertyName = "synced")]
public bool Synced { get; set; }
}
Now all you need to do is make a GET request to [application]api/postman and you'll have the latest restful API in a form that is readable by postman.
Why not use standard Swagger and use it with Postman?
What is Swagger? (Rest Web API documentation and clients enabler)
Importing Swagger files to Postman
Use Swashbuckle NuGet package in visual studio to generate Swagger for your API (Install-Package Swashbuckle -Pre)
Bonus: This solution is supported with ASP.NET Core Rest WebAPI
You will also need to update the PostmanRequestGet.cs model to get this to work.
update as follows:-
/// <summary>
/// the id of the collection that the request object belongs to
/// </summary>
[JsonProperty(PropertyName = "collectionId")]
public Guid CollectionId { get; set; }
Example using IActionDescriptorCollectionProvider with .net core 2.2, based on postman schema: https://schema.getpostman.com/json/collection/v2.0.0/collection.json
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using GR.Core.Extensions;
using GR.Core.Razor.Attributes;
using GR.Core.Razor.Models.PostmanModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Internal;
namespace GR.Core.Razor.Api
{
[AllowAnonymous]
[Route("/postman")]
public class PostmanDocsApiController : Controller
{
#region Injectable
/// <summary>
/// Inject action descriptor service
/// </summary>
private readonly IActionDescriptorCollectionProvider _provider;
#endregion
public PostmanDocsApiController(IActionDescriptorCollectionProvider provider)
{
_provider = provider;
}
/// <summary>
/// Postman collection
/// </summary>
/// <returns></returns>
[HttpGet]
[JsonProduces(typeof(PostmanCollection))]
public JsonResult Docs()
{
var postManCollection = new PostmanCollection
{
Info = new PostmanInfo
{
Id = Guid.NewGuid(),
Name = $"{GearApplication.ApplicationName} API",
Description = "api"
},
Folders = new Collection<PostmanFolder>()
};
var apiRoutes = _provider.ActionDescriptors.Items
.Where(x => x.AttributeRouteInfo?.Template?.StartsWith("api/") ?? false).ToList();
var groups = apiRoutes.GroupBy(x => x.RouteValues["Controller"])
.ToList();
foreach (var group in groups)
{
var controllerGroup = apiRoutes.FirstOrDefault(x => x.RouteValues["Controller"].Equals(group.Key)).Is<ControllerActionDescriptor>();
var type = controllerGroup.ControllerTypeInfo;
var typeSummary = type.GetSummary();
var postManFolder = new PostmanFolder
{
Name = group.Key,
Description = typeSummary,
FolderRequests = new List<PostmanFolderRequest>()
};
var domain = new Uri(HttpContext.GetAppBaseUrl());
foreach (var route in group)
{
var constraint = route.ActionConstraints[0]?.Is<HttpMethodActionConstraint>();
var methodSummary = type.GetMethod(route.RouteValues["Action"]).GetSummary();
var methodDescriptor = route.Is<ControllerActionDescriptor>();
var request = new PostmanRequest
{
Url = new PostmanRequestUrl
{
Host = domain.Authority,
Path = route.AttributeRouteInfo.Template,
Protocol = HttpContext.Request.Scheme,
Query = new List<object>()
},
Method = constraint?.HttpMethods.FirstOrDefault() ?? "GET",
Headers = new List<PostmanHeader>
{
new PostmanHeader
{
Key = "Content-Type",
Value = "application/json"
}
},
Responses = new Collection<object>(),
Description = methodSummary,
};
var inputDictionary = methodDescriptor.Parameters.ToList()
.ToDictionary(parameter => parameter.Name, parameter => parameter.ParameterType.GetDefault());
request.Body = new PostmanBodyRequest
{
Mode = "raw",
Raw = inputDictionary.SerializeAsJson()
};
postManFolder.FolderRequests.Add(new PostmanFolderRequest
{
Name = route.RouteValues["Action"],
Request = request
});
}
postManCollection.Folders.Add(postManFolder);
}
return Json(postManCollection);
}
}
}
I'm trying to create a new post in my site, but for some reason, EF throws the following error:
A relationship from the 'PostAttributeValue_Definition' AssociationSet
is in the 'Deleted' state. Given multiplicity constraints, a
corresponding 'PostAttributeValue_Definition_Source' must also in the
'Deleted' state.
Since I'm not trying to delete anything and I didn't changed or removed any value, I'm confused why I'm getting this error.
My db context contains the following:
modelBuilder.Entity<PostAttributeValue>().HasRequired<PostAttributeDefinition>(a => a.Definition).WithOptional().Map(m =>
{
m.MapKey("RelatedAttributeDefinitionId");
}).WillCascadeOnDelete(false);
/* Category(required) to PostAttributeDefinition(many) */
modelBuilder.Entity<PostAttributeDefinition>().HasRequired<Category>(a => a.OwnerCategory).WithMany(c => c.AttributeDefinitions).Map(m =>
{
m.MapKey("OwnerCategoryId");
}).WillCascadeOnDelete(true);
My publish method looks like this:
//
// POST: /Post/Publish/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Publish(int? id, PublishViewModel model)
{
if (!id.HasValue || id.Value < 1)
{
return HttpNotFound();
}
var category = this.categoryService.Find(id.Value);
if (category == null)
{
return HttpNotFound();
}
if (ModelState.IsValid)
{
List<PostAttributeValue> attributes = new List<PostAttributeValue>();
foreach (var attribute in model.Attributes)
{
attributes.Add(new PostAttributeValue()
{
Definition = attribute.Definition,
RawValue = attribute.Value.Serialize()
});
}
Post post = new Post()
{
Title = model.Title,
Description = model.Description,
City = model.City.City,
Brokerage = model.Brokerage,
Location = model.Location,
RequestedPrice = model.Price.Value,
ParentCategory = category,
Attributes = attributes,
};
this.postService.PublishPost(post);
return RedirectToAction("ImageSelection", new { id = post.PostIdentifier });
}
return View(model);
}
The error is been thrown from the Repository class Add() method, which looks like this:
public TEntity Add(TEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
try
{
var result = this.Entity.Add(entity);
this.context.SaveChanges();
return result;
}
catch
{
var deletedEntries = context.ChangeTracker.Entries().Where(e => e.State != EntityState.Added && e.State != EntityState.Unchanged);
throw;
}
}
Because the exception related to entities in deleted state, I've wrote this linq query that checks for entities that're not unchanged or added, but it returns zero results... I really
don't know why I'm getting this error.
Just to note, I'm using proxy entities, and from inspection in the debugger everything seems OK - the entire values are been filled in as excepted.
Hope somebody can help me figure it out. Thanks! :)
Edited:
The PostAttributeDefinition model class, which is a class that describes a custom attribute definition (each category can have different custom attributes - for example, "TV Shows" can have a custom attribute "Number of episodes" while Movies "IMDB rank", for example)
public class PostAttributeDefinition
{
#region Members
private Lazy<object> lazyDataValue = null;
private Lazy<PostAttributeDefinitionValidationRules> lazyValidatorValue = null;
private Type cachedDataType;
#endregion
/// <summary>
/// The filter name
/// </summary>
[Key]
public int DefinitionId { get; set; }
/// <summary>
/// The owner category
/// </summary>
[Required]
public virtual Category OwnerCategory { get; set; }
/// <summary>
/// The filter title
/// </summary>
[Required]
public string Title { get; set; }
/// <summary>
/// Metadata enum that provides extra data about the data type
/// </summary>
public PostAttributeTypeMetadata TypeMetadata { get; set; }
/// <summary>
/// Bitwise metadata that provides data about the object in display mode
/// </summary>
public PostAttributeDisplayModeMetadata DisplayModeMetadata { get; set; }
public PostAttributeEditorType EditorType { get; set; }
/// <summary>
/// The attribute raw default value
/// </summary>
[Required]
public byte[] RawDataValue { get; set; }
/// <summary>
/// The attribute raw associated validation attributes
/// </summary>
/// <remarks>
/// This field is used only by EF.
/// YES - It's against DDD rules, and I need to seperate it. setting it in TODO.
/// </remarks>
public byte[] RawValidationRules { get; set; }
/// <summary>
/// Is this field required
/// </summary>
/// <remarks>
/// This field does not relate to the validation rules since we should check it
/// only in creation / modification of the post and not in search for example.
/// </remarks>
public bool IsRequired { get; set; }
/// <summary>
/// The attribute validators
/// </summary>
public PostAttributeDefinitionValidationRules ValidationRules
{
get
{
if (lazyValidatorValue == null)
{
lazyValidatorValue = new Lazy<PostAttributeDefinitionValidationRules>(() =>
{
if (this.RawValidationRules == null || this.RawValidationRules.Length == 0)
{
return new PostAttributeDefinitionValidationRules();
}
return this.RawValidationRules.Deserialize() as PostAttributeDefinitionValidationRules;
});
}
return lazyValidatorValue.Value;
}
set
{
this.RawValidationRules = value.Serialize();
this.lazyValidatorValue = null;
}
}
/// <summary>
/// Get the stored object data type
/// </summary>
public Type ValueDataType
{
get
{
// Make sure we've loaded the serialized value
if (lazyDataValue == null)
{
RetriveDataValue();
}
return cachedDataType;
}
}
#region Value content
/// <summary>
/// Store the attribute default value
/// </summary>
/// <typeparam name="TType">The default value type</typeparam>
/// <param name="value">The default value</param>
/// <returns>Fluent style writing - returning the same object</returns>
public PostAttributeDefinition StoreDataValue<TType>(TType value)
{
// In case of empty value, we need to defaultize it
if (value == null)
{
value = value.DefaultizeNullableValueForSerialize<TType>();
}
// Store as bytes
RawDataValue = value.Serialize<TType>();
// Reset the lazy cached value
lazyDataValue = null;
// Fluent style returned value
return this;
}
/// <summary>
/// Retrive the item default value
/// </summary>
/// <typeparam name="TType">The item default value data type</typeparam>
/// <returns>The item default value</returns>
/// <exception cref="InvalidOperationException">Thrown in case the raw value is null or empty.</exception>
public TType RetriveDataValue<TType>()
{
return (TType)RetriveDataValue();
}
/// <summary>
/// Retrive the item default value
/// </summary>
/// <returns>The item default value</returns>
/// <exception cref="InvalidOperationException">Thrown in case the raw value is null or empty.</exception>
public object RetriveDataValue()
{
// Make sure that we've loaded the lazy value
if (lazyDataValue == null)
{
lazyDataValue = new Lazy<object>(() =>
{
// Deserialize
var value = RawDataValue.Deserialize();
// Remve defaultize in case we've done that (by the way, we're caching the type
// in order to retrive it in case of null value)
value = value.ReverseDefaultizeNullableValueForDeSerialize(out cachedDataType);
// Return it
return value;
});
}
// Return the cached lazy data value
return lazyDataValue.Value;
}
#endregion
}
The PostAttributeValue class, which I wish to save and causes the problems is:
public class PostAttributeValue
{
/// <summary>
/// The attribute value id
/// </summary>
[Key]
public int AttributeValueId { get; set; }
/// <summary>
/// The value owner post
/// </summary>
public virtual Post OwnerPost { get; set; }
/// <summary>
/// The value attribute definition id
/// </summary>
//public int RelatedAttributeDefinitionId { get; set; }
/// <summary>
/// The value attribute definition
/// </summary>
public virtual PostAttributeDefinition Definition { get; set; }
/// <summary>
/// The stored raw value
/// </summary>
public byte[] RawValue { get; set; }
#region Value content
/// <summary>
/// Check if there's anything stored in the raw value
/// </summary>
/// <returns>Boolean value indicates if there's anything stored in the raw value</returns>
public bool HasValue()
{
return RawValue != null;
}
/// <summary>
/// Retrive the item value
/// </summary>
/// <typeparam name="TType">The item default value data type</typeparam>
/// <returns>The item value</returns>
/// <exception cref="InvalidOperationException">Thrown in case the raw value is null or empty.</exception>
public TType RetriveValue<TType>()
{
return (TType)RetriveValue();
}
/// <summary>
/// Retrive the item value
/// </summary>
/// <returns>The item value</returns>
/// <exception cref="InvalidOperationException">Thrown in case the raw value is null or empty.</exception>
public object RetriveValue()
{
if (RawValue == null)
{
throw new InvalidOperationException("Could not deserialize the value since there's nothing in the raw value.");
}
return RawValue.Deserialize();
}
#endregion
}
Note that I'm using a ViewModel for the attributes (model.Attributes is an IEnumerable)
public class PostAttributeViewModel
{
[ReadOnly(true)]
[Editable(false)]
public PostAttributeDefinition Definition { get; set; }
[Required]
public int DefinitionId { get; set; }
[Required]
public string DefinitionVertificationToken { get; set; }
public object Value { get; set; }
}
The Definition attribute, that I'm assigning and mapping to the PostAttributeValue model is auto-filled by EF.