How to generate descriptions on enums from XML documentation for Swashbuckle? - c#

I have a model with the following enum in my .NET Core web api project:
public enum Industries
{
Undefined = 0,
/// <summary>
/// Agriculture, Forestry, Fishing and Hunting
/// </summary>
AgricultureForestryFishingAndHunting = 1,
Mining = 2,
Utilities = 3,
Construction = 4,
/// <summary>
/// Computer and Electronics Manufacturing
/// </summary>
ComputerAndElectronicsManufacturing = 5,
/// <summary>
/// Other Manufacturing
/// </summary>
OtherManufacturing = 6,
Wholesale = 7,
Retail = 8,
/// <summary>
/// Transportation and Warehousing
/// </summary>
TransportationAndWarehousing = 9,
Publishing = 10,
Software = 11,
Telecommunications = 12,
Broadcasting = 13,
/// <summary>
/// Information Services and Data Processing
/// </summary>
InformationServicesAndDataProcessing = 14,
/// <summary>
/// Other Information Industry
/// </summary>
OtherInformationIndustry = 15,
/// <summary>
/// Finance and Insurance
/// </summary>
FinanceAndInsurance = 16,
/// <summary>
/// Real Estate, Rental and Leasing
/// </summary>
RealEstateRentalAndLeasing = 17,
/// <summary>
/// College, University, and Adult Education
/// </summary>
CollegeUniversityAndAdultEducation = 18,
/// <summary>
/// Primary/Secondary (K-12) Education
/// </summary>
PrimarySecondaryK12Education = 19,
/// <summary>
/// Other Education Industry
/// </summary>
OtherEducationIndustry = 20,
/// <summary>
/// Health Care and Social Assistance
/// </summary>
HealthCareAndSocialAssistance = 21,
/// <summary>
/// Arts, Entertainment, and Recreation
/// </summary>
ArtsEntertainmentAndRecreation = 22,
/// <summary>
/// Hotel and Food Services
/// </summary>
HotelAndFoodServices = 23,
/// <summary>
/// Government and Public Administration
/// </summary>
GovernmentAndPublicAdministration = 24,
/// <summary>
/// Legal Services
/// </summary>
LegalServices = 25,
/// <summary>
/// Scientific or Technical Services
/// </summary>
ScientificorTechnicalServices = 26,
Homemaker = 27,
Military = 28,
Religious = 29,
/// <summary>
/// Other Industry
/// </summary>
OtherIndustry = 30
}
I then wire up swashbuckle to include the XML documentation file:
services.AddSwaggerGen(c =>
{
c.IncludeXmlComments(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
"MySolution.xml"), true);
c.IncludeXmlComments(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
"MySolution.Client.xml"), true);
c.IncludeXmlComments(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
"MySolution.Common.xml"), true);
c.DescribeAllEnumsAsStrings();
c.SwaggerDoc("v1",
new Info {Title = "My Solution", Version = "v1"});
c.DescribeAllParametersInCamelCase();
c.DescribeStringEnumsInCamelCase();
c.IgnoreObsoleteProperties();
c.UseReferencedDefinitionsForEnums();
c.CustomSchemaIds(x => x.FullName);
});
When running this and viewing the swagger.json document, I don't see my XML comments for the enum values at all, just the values:
"definitions": {
...
"MySolution.Common.Models.Industries": {
"enum": [
"undefined",
"agricultureForestryFishingAndHunting",
"mining",
"utilities",
"construction",
"computerAndElectronicsManufacturing",
"otherManufacturing",
"wholesale",
"retail",
"transportationAndWarehousing",
"publishing",
"software",
"telecommunications",
"broadcasting",
"informationServicesAndDataProcessing",
"otherInformationIndustry",
"financeAndInsurance",
"realEstateRentalAndLeasing",
"collegeUniversityAndAdultEducation",
"primarySecondaryK12Education",
"otherEducationIndustry",
"healthCareAndSocialAssistance",
"artsEntertainmentAndRecreation",
"hotelAndFoodServices",
"governmentAndPublicAdministration",
"legalServices",
"scientificorTechnicalServices",
"homemaker",
"military",
"religious",
"otherIndustry"],
"type": "string"
}
}
What am I missing to make this work? I'm using v 2.5.0 of Swashbuckle, which looks like the latest and greatest.

Comments for the enum values are not supported by the OAS (OpenAPI-Specification) :
5.5.1.1. Valid values
The value of this keyword MUST be an array. This array MUST have at
least one element. Elements in the array MUST be unique.
Elements in the array MAY be of any type, including null.
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#items-object
https://datatracker.ietf.org/doc/html/draft-fge-json-schema-validation-00#section-5.5.1

Related

How to pull a List of objects from FormDataCollection

I'm calling my API via a post. I am trying to get the values out from the FormDataCollection, but I cannot figure out how to get the list called ResourcesInfo?
UPDATE:
Here are the raw values:
Here is my setup:
What am I doing wrong here?
var resourceInfo2 = form.Get("ResourcesInfo");
var resourceInfo = JsonConvert.DeserializeObject<IList<SchedulerConflictResourceInfoModel>>(form.Get("ResourcesInfo"));
namespace WebPortal.MVC.Areas.Scheduler.Models
{
using System;
using System.Collections.Generic;
/// <summary>
/// Scheduler conflict param model
/// Obtain the values after the start or end date changes to check for conflicts
/// </summary>
public class SchedulerConflictParamModel
{
/// <summary>
/// Gets or sets the start date.
/// </summary>
/// <value>
/// The start date.
/// </value>
public DateTime StartDate { get; set; }
/// <summary>
/// Gets or sets the end date.
/// </summary>
/// <value>
/// The end date.
/// </value>
public DateTime EndDate { get; set; }
/// <summary>
/// Gets or sets the resources information.
/// </summary>
/// <value>
/// The resources information.
/// </value>
public IList<SchedulerConflictResourceInfoModel> ResourcesInfo { get; set; }
}
}
namespace WebPortal.MVC.Areas.Scheduler.Models
{
/// <summary>
/// Scheduler conflict resource info model
/// Holds the resource type id and resource id to check for conflicts
/// </summary>
public class SchedulerConflictResourceInfoModel
{
/// <summary>
/// Gets or sets the resource type identifier.
/// </summary>
/// <value>
/// The resource type identifier.
/// </value>
public int ResourceTypeId { get; set; }
/// <summary>
/// Gets or sets the resource identifier.
/// </summary>
/// <value>
/// The resource identifier.
/// </value>
public int ResourceId { get; set; }
}
}
function schedulerCheckForConflicts2(model) {
var deferred = $.Deferred()
let apiUrl = BuildSafeURL("api/SchedulerData/SchedulerCheckForConflicts", null)
$.post(apiUrl, {
StartDate: model.StartDate,
EndDate: model.EndDate,
ResourcesInfo: model.ResourcesInfo
})
.done(function (conflicts) {
DevExpress.ui.notify("called server... ", "warning", 1200)
deferred.resolve(conflicts)
})
.fail(function (error) {
genericErrorMessage()
console.log("error⚠️", error)
})
return deferred.promise();
}
/// <summary>
/// Gets the items scheduler file manager.
/// </summary>
/// <param name="form">The form.</param>
/// <returns>File system items</returns>
[HttpPost]
[WebAPIValidateAntiForgeryToken]
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
public async Task<HttpResponseMessage> SchedulerCheckForConflicts(FormDataCollection form)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{
var startDate = form.Get("StartDate");
var endDate = form.Get("EndDate");
var resourceInfo2 = form.Get("ResourcesInfo");
var resourceInfo = JsonConvert.DeserializeObject<IList<SchedulerConflictResourceInfoModel>>(form.Get("ResourcesInfo"));
var schedulerConflictParamModel = new SchedulerConflictParamModel();
return Request.CreateResponse(new List<int>
{
1, 2, 3
});
}

How to replace region with own enum in AmazonEC2Client in c#?

In my aspnet core 3.1 project I am using client for aws for getting all instance list for me.
In constructor AmazonEC2Client third parameter is class which is accepting RegionEndPoint, and I would like to put enum instead of RegionEndPoint.
My method looks like:
public static async Task<List<string>> AwsList(string awsAccessKeyId, string
awsSecretAccessKey) // should AwsRegion region as 3rd paramater
{
AmazonEC2Client client = new AmazonEC2Client(awsAccessKeyId,awsSecretAccessKey,
RegionEndpoint.EUWest1); // replace RegionEndpoint.EUWest1 with enum paramater
bool done = false;
var instanceIds = new List<string>();
DescribeInstanceTypesRequest request = new DescribeInstanceTypesRequest();
while (!done)
{
DescribeInstanceTypesResponse response = await
client.DescribeInstanceTypesAsync(request);
foreach ( var instanceType in response.InstanceTypes.Where(x =>
x.MemoryInfo.SizeInMiB >= 2048 && x.VCpuInfo.DefaultVCpus >= 2))
{
instanceIds.Add(instanceType.InstanceType);
}
request.NextToken= response.NextToken;
if (response.NextToken == null)
{
done = true;
}
}
return instanceIds;
}
I would like to 3rd parameter which is enum to my method and replace Region with enum. As there is no constructor which accepting enum, how I can specify AmazonECwClient constructor and specify enum using extension methods (if possible)
My enum which I want implement and replace.
public enum AwsRegion
{
/// <summary>The US East (Virginia) endpoint.</summary>
USEast1 = 1,
/// <summary>The US East (Ohio) endpoint.</summary>
USEast2 = 2,
/// <summary>The US West (N. California) endpoint.</summary>
USWest1 = 3,
/// <summary>The US West (Oregon) endpoint.</summary>
USWest2 = 4,
/// <summary>The EU West (Ireland) endpoint.</summary>
EUWest1 = 5,
/// <summary>The EU Central (Frankfurt) endpoint.</summary>
EUCentral1 = 6,
/// <summary>The Asia Pacific (Tokyo) endpoint.</summary>
APNortheast1 = 7,
/// <summary>The Asia Pacific (Seoul) endpoint.</summary>
APNortheast2 = 8,
/// <summary>The Asia Pacific (Mumbai) endpoint.</summary>
APSouth1 = 9,
/// <summary>The Asia Pacific (Singapore) endpoint.</summary>
APSoutheast1 = 10,
/// <summary>The Asia Pacific (Sydney) endpoint.</summary>
APSoutheast2 = 11,
/// <summary>The South America (Sao Paulo) endpoint.</summary>
SAEast1 = 12,
/// <summary>The US GovCloud West (Oregon) endpoint.</summary>
USGovCloudWest1 = 13,
/// <summary>The China (Beijing) endpoint.</summary>
CNNorth1 = 14
}
I think this source code of aws-sdk-net RegionEndpoint.cs will help you deal with region mapping.
Even AWSSDK has a dictionary in order to quickly convert a region string to its static RegionEndpoint object.
private static Dictionary<string, RegionEndpoint> _hashBySystemName = new Dictionary<string, RegionEndpoint>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Gets the region based on its system name like "us-west-1"
/// </summary>
/// <param name="systemName">The system name of the service like "us-west-1"</param>
/// <returns></returns>
public static RegionEndpoint GetBySystemName(string systemName)
{
// ...skip
if (_hashBySystemName.TryGetValue(systemName, out regionEndpoint))
return regionEndpoint;
// ...skip
}
Which means you can have your own dictionary and a converting method.
private static Dictionary<AwsRegion, RegionEndpoint> _awsRegionDict = new Dictionary<AwsRegion, RegionEndpoint>(StringComparer.OrdinalIgnoreCase);
public static RegionEndpoint ConvertByAwsRegion(AwsRegion emAwsRegion)
{
if (_awsRegionDict.TryGetValue(emAwsRegion, out regionEndpoint))
return regionEndpoint;
return RegionEndpoint.EUWest1; // your default region
}
Offering another extension method would make it easiler to use.
public static class RegionEndpointExtension
{
public RegionEndpoint ToRegionEndpoint(this AwsRegion region)
{
return AwsRegionProvider.ConvertByAwsRegion(region)
}
}
Thus, use your AwsRegion enum as 3rd argument, then calling ConvertByAwsRegion or ToRegionEndpoint converting method on demand.

Understanding Serialization

Context
I'm trying to understand how to use Serialization, never used it previously.
Right now I have a populate method in my singleton object (Main class) that basically adds a few member objects to a list of members using my addMember methods
I want to serialize this Members List Once I can serialize and deserialize the list I can delete my populate method.
Questions
HOW do I serialize this list so that the members list is deserialized upon startup?
WHERE do I serialize specifically? Do I serialize when I'm creating a new member, or do I just serialize the list at shutdown and deserialize at startup.
Since member information can be edited, how do I serialize the updates and overwrite the previously held data?
Code Listings
I'm kinda new to Serialization, but here's my code, I'm using a method for this because I I think it's cleaner this way, using ISerializable in my main class. Here's a few snippets from my main class, keep in mind I have tons of comments in my code, that's kinda why I didn't post this previously:
namespace HillRacingGraded
{
[Serializable]
public class HillRacing : ISerializable
{
/// <summary>
/// Singleton object hillracing
/// </summary>
private static HillRacing hillracing;
GUIUtility gui = new GUIUtility();
/// <summary>
/// Instance property returns the singleton instance
/// </summary>
public static HillRacing Instance
{
get
{
if (hillracing == null)
hillracing = new HillRacing();
return hillracing;
}
}
/// <summary>
/// public Property that returns races list, can be accessed by other classes.
/// </summary>
public List<BaseRace> Races
{
get
{
return races;
}
set
{
races = value;
}
}
/// <summary>
/// public Property that returns members list, can be accessed by other classes.
/// </summary>
public List<BaseMember> Members
{
get
{
return members;
}
set
{
members = value;
}
}
/// <summary>
/// instantiate the list of members
/// </summary>
private List<BaseMember> members; //I WANT TO SERIALIZE THIS
/// <summary>
/// instantiate the list of races
/// </summary>
private List<BaseRace> races; //I WANT TO SERIALIZE THIS
/// <summary>
/// Default constructor for hillracing.
/// </summary>
public HillRacing()
{
//members is a new list of the BaseMember objects.
//races is a new list of the BaseRace objects.
members = new List<BaseMember>();
races = new List<BaseRace>();
//call the populate method on launch, mostly for testing purposes.
Populate();
}
/// <summary>
/// Hillracing constructor for serialization
/// </summary>
/// <param name="info"></param>
/// <param name="ctxt"></param>
public HillRacing(SerializationInfo info, StreamingContext ctxt)
{
this.members = (List<BaseMember>)info.GetValue("Members", typeof(List<BaseMember>));
this.races = (List<BaseRace>)info.GetValue("Races", typeof(List<BaseRace>));
}
/// <summary>
/// get object data
/// </summary>
/// <param name="info"></param>
/// <param name="ctxt"></param>
public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
{
info.AddValue("Members", this.members);
}
/// <summary>
/// Adds a new junior member to the list of all members.
/// </summary>
/// <param name="stringfirstname">first name of the member</param>
/// <param name="stringlastname">last name of the member</param>
/// <param name="stringmiddlename">middle name of the member</param>
/// <param name="stringtitle">title of the member</param>
/// <param name="strst">street of the member</param>
/// <param name="strtwn">Town of the member</param>
/// <param name="strpc">Postcode of the member</param>
/// <param name="strEmail">email of the member</param>
/// <param name="intMobile">Mobile of the member</param>
/// <param name="intHome">Home phone of the member</param>
/// <param name="shrnumber">ID number of the member</param>
/// <param name="memtype">Type of the member</param>
/// <param name="username">username of the member</param>
/// <param name="noracesrun">number of races that the member has run</param>
/// <param name="perraceswon">percentage of races that the member has won</param>
/// <param name="mempic">image of the member</param>
/// <param name="memclub">the club the member is part of</param>
/// <param name="gender">the gender of the member</param>
/// <param name="memexp">the experience level the member has</param>
/// <param name="yearofbirth">the year of birth the member was born in</param>
/// <param name="monthofbirth">the month of birth the member was born in</param>
/// <param name="dayofbirth">the day of birth the member was born on</param>
public void addJunior(string stringfirstname, string stringlastname, string stringmiddlename, string stringtitle, string strst, string strtwn, string strpc, string strEmail, int intMobile, int intHome,
string shrnumber, string memtype, string username, string password, int noracesrun, float perraceswon, string mempic, string memclub, string gender, int memexp, int yearofbirth, int monthofbirth, int dayofbirth, string nextofkin, string docName, string docTel, string healthIssues, string parentalConsent)
{
// create a new member with the entered parameters to add to the list.
JuniorMember newMember = new JuniorMember(stringfirstname, stringlastname, stringmiddlename, stringtitle, strst, strtwn, strpc, strEmail, intMobile, intHome, shrnumber, memtype, username, password, noracesrun, perraceswon, mempic, memclub, gender, memexp, yearofbirth, monthofbirth, dayofbirth,nextofkin,docName,docTel,healthIssues,parentalConsent);
//use add functionality of list to add to the list.
members.Add(newMember);
}
/// <summary>
///
/// </summary>
/// <param name="stringfirstname">first name of the member</param>
/// <param name="stringlastname">last name of the member</param>
/// <param name="stringmiddlename">middle name of the member</param>
/// <param name="stringtitle">title of the member</param>
/// <param name="strst">street of the member</param>
/// <param name="strtwn">Town of the member</param>
/// <param name="strpc">Postcode of the member</param>
/// <param name="strEmail">email of the member</param>
/// <param name="intMobile">Mobile of the member</param>
/// <param name="intHome">Home phone of the member</param>
/// <param name="shrnumber">ID number of the member</param>
/// <param name="memtype">Type of the member</param>
/// <param name="username">username of the member</param>
/// <param name="noracesrun">number of races that the member has run</param>
/// <param name="perraceswon">percentage of races that the member has won</param>
/// <param name="mempic">image of the member</param>
/// <param name="memclub">the club the member is part of</param>
/// <param name="gender">the gender of the member</param>
/// <param name="memexp">the experience level the member has</param>
/// <param name="yearofbirth">the year of birth the member was born in</param>
/// <param name="monthofbirth">the month of birth the member was born in</param>
/// <param name="dayofbirth">the day of birth the member was born on</param>
/// <param name="nextofkin">The next family member contact</param>
/// <param name="docName">The name of the members doctor</param>
/// <param name="docTel">A telephone number for the doctor</param>
/// <param name="healthIssues">the health issues this member has.</param>
public void addSenior(string stringfirstname, string stringlastname, string stringmiddlename, string stringtitle, string strst, string strtwn, string strpc, string strEmail, int intMobile, int intHome,
string shrnumber, string memtype, string username, string password, int noracesrun, float perraceswon, string mempic, string memclub, string gender, int memexp, int yearofbirth, int monthofbirth, int dayofbirth, string nextofkin, string docName, string docTel, string healthIssues)
{
//create a new member with the entered parameters to add to the list.
SeniorMember newMember = new SeniorMember(stringfirstname, stringlastname, stringmiddlename, stringtitle, strst, strtwn, strpc, strEmail, intMobile, intHome, shrnumber, memtype, username, password, noracesrun, perraceswon, mempic, memclub, gender, memexp, yearofbirth, monthofbirth, dayofbirth,docName,docTel,healthIssues);
//use standard list functionality of list to add this new member to the list.
members.Add(newMember);
}
Here is my Serialization method in the Serializer class:
public void SerializeObject(string filename, object objectToSerialize)
{
Stream stream = File.Open(filename + ".bin", FileMode.Create);
BinaryFormatter bFormatter = new BinaryFormatter();
bFormatter.Serialize(stream, objectToSerialize);
stream.Close();
}
Problem is, I don't know how to actually use this.
Also have a deserializer:
public HillRacing DeSerializeObject(string filename)
{
HillRacing hillracing;
Stream stream = File.Open(filename + ".bin", FileMode.Open);
BinaryFormatter bFormatter = new BinaryFormatter();
hillracing = (HillRacing)bFormatter.Deserialize(stream);
stream.Close();
return hillracing;
}
Although you have done most of the part i suggest a little generics make it multiuse as
public static class StreamUtilities
{
public static T GetObject<T>(Byte[] rawimage) where T : class
{
try
{
MemoryStream memStream = new MemoryStream();
BinaryFormatter binForm = new BinaryFormatter();
memStream.Write(rawimage, 0, rawimage.Length);
memStream.Seek(0, SeekOrigin.Begin);
return binForm.Deserialize(memStream) as T;
}
catch (Exception ex)
{
return null;
}
}
public static Byte[] Serialize<T>(this T obj) where T:class
{
if (obj == null)
return null;
BinaryFormatter bf = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
bf.Serialize(ms, obj);
return ms.ToArray();
}
}
then in your main class or whereever you want it to use include to namespace where the above extention method is then use
Object1 a=new Object1();// any serializable object
serializedbytes=a.Serialize<Object1>();
//deserialize
Object b=StreamUtilities.GetObject<Object1>(serializedbytes);
The above extention method will allow to seriailize/Deserialize any serializable Object.

XmlEnum Multiple name for a single value

I have this scenario, declaring a enum type. Like this:
[Serializable]
[XmlTypeAttribute(Namespace = "urn:un:unece:uncefact:codelist:specification:5639:1988")]
public enum LanguageCodeContentType {
/// <summary>
/// Afar
/// </summary>
[XmlEnum(Name = "AA")]
AA,
/// <summary>
/// Abkhazian
/// </summary>
[XmlEnum(Name = "AB")]
AB,
/// <summary>
/// Afrikaans
/// </summary>
[XmlEnum(Name = "AF")]
AF,
[...]
}
Now what I need is declaring multiple name for each value of the enum. Something like this
[Serializable]
[XmlTypeAttribute(Namespace = "urn:un:unece:uncefact:codelist:specification:5639:1988")]
public enum LanguageCodeContentType {
/// <summary>
/// Afar
/// </summary>
[XmlEnum(Name = "AA"),XmlEnum(Name = "aa")]
AA,
/// <summary>
/// Abkhazian
/// </summary>
[XmlEnum(Name = "AB", XmlEnum(Name = "bb"))]
AB,
/// <summary>
/// Afrikaans
/// </summary>
[XmlEnum(Name = "AF"), XmlEnum(Name = "af")]
AF,
[...]
}
It's been a will, but I came along to this issue today.
I solved this by introducing multiple entries in my enumeration.
public enum LanguageCodeContentType {
AA = 0,
aa = 0,
AB = 1,
ab = 1,
AF = 2,
af = 2,
[...]
}
By this ether, "aa" or "AA" could be serialized.

How to Get email id using asp.net mvc4 openid api [closed]

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 9 years ago.
Looking to get the email id along with the openid while
Using the Internet template for MVC4
This is available for auth using google but not for facebook
wondering how to get/request the email id in the extraData dictionary
Looking at the code in AspNetWebStack project at http://aspnetwebstack.codeplex.com/, it looks like
OAuthWebSecurity.RegisterFacebookClient()
makes use of FacebookClient in DotNetOpenAuth.AspNet.dll hosted at https://github.com/AArnott/dotnetopenid
and the code in FacebookClient.GetUserData() has
var userData = new NameValueCollection();
userData.AddItemIfNotEmpty("id", graphData.Id);
userData.AddItemIfNotEmpty("username", graphData.Email);
userData.AddItemIfNotEmpty("name", graphData.Name);
userData.AddItemIfNotEmpty("link", graphData.Link == null ? null : graphData.Link.AbsoluteUri);
userData.AddItemIfNotEmpty("gender", graphData.Gender);
userData.AddItemIfNotEmpty("birthday", graphData.Birthday);
return userData;
which should return the email-id in username but it's not being returned
any help is appreciated
thanks
The provided Facebook OAuth client will not let you get anything beyond the default info. To get anything else, you need to be able to change the value of the scope parameter, something the included client doesn't allow. So, to get around this and still use the other boilerplate code the Internet template provides, you need to implement a custom OAuth client that follows the same pattern.
Since the entire ASP.NET source is open source, as is the OAuth library DotNetOpenAuth, you can actually look into the OAuth library and see exactly how the Facebook provider is built. Using that, I was able to come up with this:
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Web;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.AspNet;
using DotNetOpenAuth.AspNet.Clients;
using Validation;
using Newtonsoft.Json;
namespace OAuthProviders
{
/// <summary>
/// The facebook client.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Facebook", Justification = "Brand name")]
public sealed class FacebookScopedClient : OAuth2Client
{
#region Constants and Fields
/// <summary>
/// The authorization endpoint.
/// </summary>
private const string AuthorizationEndpoint = "https://www.facebook.com/dialog/oauth";
/// <summary>
/// The token endpoint.
/// </summary>
private const string TokenEndpoint = "https://graph.facebook.com/oauth/access_token";
/// <summary>
/// The _app id.
/// </summary>
private readonly string appId;
/// <summary>
/// The _app secret.
/// </summary>
private readonly string appSecret;
private readonly string scope;
#endregion
#region Constructors and Destructors
/// <summary>
/// Initializes a new instance of the <see cref="FacebookScopedClient"/> class.
/// </summary>
/// <param name="appId">
/// The app id.
/// </param>
/// <param name="appSecret">
/// The app secret.
/// </param>
/// <param name="scope">
/// The scope (requested Facebook permissions).
/// </param>
public FacebookScopedClient(string appId, string appSecret, string scope)
: base("facebook")
{
Requires.NotNullOrEmpty(appId, "appId");
Requires.NotNullOrEmpty(appSecret, "appSecret");
Requires.NotNullOrEmpty(scope, "scope");
this.appId = appId;
this.appSecret = appSecret;
this.scope = scope;
}
#endregion
#region Methods
/// <summary>
/// The get service login url.
/// </summary>
/// <param name="returnUrl">
/// The return url.
/// </param>
/// <returns>An absolute URI.</returns>
protected override Uri GetServiceLoginUrl(Uri returnUrl)
{
// Note: Facebook doesn't like us to url-encode the redirect_uri value
var builder = new UriBuilder(AuthorizationEndpoint);
builder.AppendQueryArgument("client_id", this.appId);
builder.AppendQueryArgument("redirect_uri", returnUrl.AbsoluteUri);
builder.AppendQueryArgument("scope", this.scope);
return builder.Uri;
}
/// <summary>
/// The get user data.
/// </summary>
/// <param name="accessToken">
/// The access token.
/// </param>
/// <returns>A dictionary of profile data.</returns>
protected override IDictionary<string, string> GetUserData(string accessToken)
{
FacebookGraphData graphData;
var request =
WebRequest.Create("https://graph.facebook.com/me?access_token=" + UriDataStringRFC3986(accessToken));
using (var response = request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
graphData = OAuthJsonHelper.Deserialize<FacebookGraphData>(responseStream);
}
}
// this dictionary must contains
var userData = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(graphData.Id)) { userData.Add("id", graphData.Id); }
if (!string.IsNullOrEmpty(graphData.Email)) { userData.Add("username", graphData.Email); }
if (!string.IsNullOrEmpty(graphData.Name)) { userData.Add("name", graphData.Name); }
if (graphData.Link != null && !string.IsNullOrEmpty(graphData.Link.AbsoluteUri)) { userData.Add("link", graphData.Link == null ? null : graphData.Link.AbsoluteUri); }
if (!string.IsNullOrEmpty(graphData.Gender)) { userData.Add("gender", graphData.Gender); }
if (!string.IsNullOrEmpty(graphData.Birthday)) { userData.Add("birthday", graphData.Birthday); }
return userData;
}
/// <summary>
/// Obtains an access token given an authorization code and callback URL.
/// </summary>
/// <param name="returnUrl">
/// The return url.
/// </param>
/// <param name="authorizationCode">
/// The authorization code.
/// </param>
/// <returns>
/// The access token.
/// </returns>
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
{
// Note: Facebook doesn't like us to url-encode the redirect_uri value
var builder = new UriBuilder(TokenEndpoint);
builder.AppendQueryArgument("client_id", this.appId);
builder.AppendQueryArgument("redirect_uri", NormalizeHexEncoding(returnUrl.AbsoluteUri));
builder.AppendQueryArgument("client_secret", this.appSecret);
builder.AppendQueryArgument("code", authorizationCode);
builder.AppendQueryArgument("scope", this.scope);
using (WebClient client = new WebClient())
{
string data = client.DownloadString(builder.Uri);
if (string.IsNullOrEmpty(data))
{
return null;
}
var parsedQueryString = HttpUtility.ParseQueryString(data);
return parsedQueryString["access_token"];
}
}
/// <summary>
/// Converts any % encoded values in the URL to uppercase.
/// </summary>
/// <param name="url">The URL string to normalize</param>
/// <returns>The normalized url</returns>
/// <example>NormalizeHexEncoding("Login.aspx?ReturnUrl=%2fAccount%2fManage.aspx") returns "Login.aspx?ReturnUrl=%2FAccount%2FManage.aspx"</example>
/// <remarks>
/// There is an issue in Facebook whereby it will rejects the redirect_uri value if
/// the url contains lowercase % encoded values.
/// </remarks>
private static string NormalizeHexEncoding(string url)
{
var chars = url.ToCharArray();
for (int i = 0; i < chars.Length - 2; i++)
{
if (chars[i] == '%')
{
chars[i + 1] = char.ToUpperInvariant(chars[i + 1]);
chars[i + 2] = char.ToUpperInvariant(chars[i + 2]);
i += 2;
}
}
return new string(chars);
}
/// <summary>
/// The set of characters that are unreserved in RFC 2396 but are NOT unreserved in RFC 3986.
/// </summary>
private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" };
internal static string UriDataStringRFC3986(string value)
{
// Start with RFC 2396 escaping by calling the .NET method to do the work.
// This MAY sometimes exhibit RFC 3986 behavior (according to the documentation).
// If it does, the escaping we do that follows it will be a no-op since the
// characters we search for to replace can't possibly exist in the string.
var escaped = new StringBuilder(Uri.EscapeDataString(value));
// Upgrade the escaping to RFC 3986, if necessary.
for (int i = 0; i < UriRfc3986CharsToEscape.Length; i++)
{
escaped.Replace(UriRfc3986CharsToEscape[i], Uri.HexEscape(UriRfc3986CharsToEscape[i][0]));
}
// Return the fully-RFC3986-escaped string.
return escaped.ToString();
}
#endregion
}
}
There are a few dependent libraries required to make this work as-is, all of which are available on NuGet. You already have DotNetOpenAuth; http://nuget.org/packages/Validation/ is another. The OAuthJsonHelper is a copy of an internal class used by DotNetOpenAuth - to get this provider to work I had to re-implement it in my own namespace:
using System;
using System.IO;
using System.Runtime.Serialization.Json;
using Validation;
namespace OAuthProviders
{
/// <summary>
/// The JSON helper.
/// </summary>
internal static class OAuthJsonHelper
{
#region Public Methods and Operators
/// <summary>
/// The deserialize.
/// </summary>
/// <param name="stream">
/// The stream.
/// </param>
/// <typeparam name="T">The type of the value to deserialize.</typeparam>
/// <returns>
/// The deserialized value.
/// </returns>
public static T Deserialize<T>(Stream stream) where T : class
{
Requires.NotNull(stream, "stream");
var serializer = new DataContractJsonSerializer(typeof(T));
return (T)serializer.ReadObject(stream);
}
#endregion
}
}
This is all provided as-is, with no guarantee that it will work via copy/paste - it's up to you to figure out how to integrate it into your project.

Categories

Resources