For an application created and configured in Azure Active Directory I am able to request and obtain a token. When I am trying to use this token to read OPTIONS from Exchange ActiveSync, the response is 401 unauthorized. The source code is below. Obviously I am doing something wrong. Any help is greatly appreciated!
Thank you!
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace FAC.ActiveSync.ModernAuth
{
internal partial class Program
{
private static readonly AzureApp app = new AzureApp
{
TenantId = "***",
ClientId = "***",
ClientSecretId = "***",
ClientSecretValue = "***"
};
static async Task Main()
{
var response = await GetOptions();
}
private static async Task<AccessTokenModel> GetToken()
{
var data = new Dictionary<string, string>
{
{ "grant_type", "client_credentials"},
{ "scope", "https://outlook.office365.com/.default"},
{ "client_id", app.ClientId},
{ "client_secret", app.ClientSecretValue}
};
using (var client = new HttpClient())
{
HttpResponseMessage response =
await client.PostAsync(app.UrlGetToken,
new FormUrlEncodedContent(data));
return await response.Content.ReadAsAsync<AccessTokenModel>();
}
}
private static async Task<string> GetOptions()
{
var accessToken = await GetToken();
var request = new HttpRequestMessage(
HttpMethod.Options,
"https://outlook.office365.com/Microsoft-Server-ActiveSync");
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer",
accessToken.access_token);
var response = await client.SendAsync(request);
return await response.Content.ReadAsStringAsync();
}
}
}
public class AccessTokenModel
{
public string access_token { get; set; }
public string token_type { get; set; }
public int expires_in { get; set; }
public int ext_expires_in { get; set; }
}
public class AzureApp
{
public string TenantId { get; set; }
public string ClientId { get; set; }
public string ClientSecretValue { get; set; }
public string ClientSecretId { get; set; }
public string UrlGetToken
{
get
{
return $"https://login.microsoftonline.com/{TenantId}/oauth2/v2.0/token";
}
}
public string UrlAuthorize
{
get
{
return $"https://login.microsoftonline.com/{TenantId}/oauth2/v2.0/authorize";
}
}
}
}
Related
I want to Save jwt token in the database I share the code of the controller where token generation is done but I don't know how to save the token or that code will work or nor
this is my controller where use jwt token
public class LoginController: Controller
{
private readonly JwtAuthContext _context;
private IConfiguration _config;
public LoginController(IConfiguration config, JwtAuthContext
context)
{
_config = config;
_context = context;
}
[Route("api/Register")]
[HttpPost]
public IActionResult Post([FromBody] Register register)
{
if (ModelState.IsValid)
{
_context.Add(register);
_context.SaveChanges();
}
Console.WriteLine(register);
var ttt = _context.Registers.ToList();
return Ok(new { result = ttt });
}
[HttpPost]
public IActionResult Login([FromBody] Login Login)
{
var user = Authenticate(Login);
if (user != null)
{
var token = Generate(user);
_context.SaveChanges();
return Ok(token);
}
return NotFound("User not found");
}
private string Generate(Register user)
{
var securityKey = new
SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey,
SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Email),
new Claim(ClaimTypes.Email, user.FullName),
new Claim(ClaimTypes.Role, user.Role)
};
var token = new JwtSecurityToken(_config["Jwt:Issuer"],
_config["Jwt:Audience"],
claims,
expires: DateTime.Now.AddMinutes(15),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
private Register Authenticate(Login Login)
{
var currentUser = _context.Registers.FirstOrDefault(o =>
o.Email.ToLower() == Login.Email.ToLower() && o.Password == Login.Password);
if (currentUser != null)
{
return currentUser;
}
return null;
}
this is my login model where I create a table of login
public class login{
public int LoginId{get;set;}
public string Email{get;set;}
public string Password{get;set;}
}
this is my register model where I can create a register model
public class Register{
public int Id{get;set;}
public string FullName{get;set;}
public string Email{get;set;}
public string Password{get;set;}
}
-------------
JwtAuthContext
--------------
public class JwtAuthContext : DbContext
{
public JwtAuthContext(DbContextOptions<JwtAuthContext> options)
: base(options)
{
}
public DbSet<Login> Logins { get; set; }
public DbSet<Register> Registers { get; set; }
public DbSet<AuthenticationToken> authenticationTokens { get;
set; }
}
This is my AuthenticationToken Model
public class AuthenticationToken
{
public string Token{get;set;}
}
Try this.
if (user != null)
{
var token = Generate(user);
_context.authenticationTokens.Add(token); // just add this line
_context.SaveChanges();
return Ok(token);
}
I know this is a very basic question, but I'm creating a browser in wpf (I'm almost done with it) using CEFSharp, but I hit a snag with logging into google on some accounts. I get a "This browser or app may not be secure." message. Now, I've researched using OAuth to get a token for accessing and using certain features of Google, but not just how to log in as a whole. I just want the user to be able to log into Google, just like they would on a Chrome browser (without the Sync option, of course).
I registered my application with Google Console, and received a ClientId and ClientSecret, and I created a window to be called when the sign-in button is clicked on the google page that tries to get the token, but when the user types in their e-mail, they get the same message (See Below).
I'm not sure if there's an API that I can use or something that can let my user login to google through my browser without having to call the Google Chrome browser itself, because that defeats the purpose of my browser being self-sufficient. I've been beating my head on this for days. Can anyone help?
Below is the xaml for my Google Login window:
<Window x:Class="MyProject.Windows.GoogleLoginWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpf="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
xmlns:local="clr-namespace:NP_Browser.Windows"
mc:Ignorable="d"
Title="Google Login Window" Height="450" Width="800" Icon="../Images/NPBrowserLogo.ico">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="btv" />
</Window.Resources>
<Grid>
<DockPanel Visibility="{Binding State.IsSigned, Converter={StaticResource btv}}">
<Label Content="{Binding State.Token.Name}" />
</DockPanel>
<Grid Visibility="{Binding State.IsNotSigned, Converter={StaticResource btv}}">
<wpf:ChromiumWebBrowser x:Name="Wb" FontSize="16"/>
</Grid>
</Grid>
Below is the code-behind for my Google Login window:
namespace MyProject.Windows
{
public partial class GoogleLoginWindow : Window
{
public GoogleLoginWindow()
{
InitializeComponent();
State = new OAuthState();
DataContext = this;
Topmost = true;
var thread = new Thread(HandleRedirect);
thread.Start();
}
public OAuthState State { get; }
private async void HandleRedirect()
{
State.Token = null;
var request = OAuthRequest.BuildLoopbackRequest();
var listener = new HttpListener();
listener.Prefixes.Add(request.RedirectUri);
listener.Start();
// note: add a reference to System.Windows.Presentation and a 'using System.Windows.Threading' for this to compile
await Dispatcher.BeginInvoke(() =>
{
Wb.Address = request.AuthorizationRequestUri;
});
// here, we'll wait for redirection from our hosted webbrowser
var context = await listener.GetContextAsync();
// browser has navigated to our small http server answer anything here
string html = string.Format("<html><body></body></html>");
var buffer = Encoding.UTF8.GetBytes(html);
context.Response.ContentLength64 = buffer.Length;
var stream = context.Response.OutputStream;
var responseTask = stream.WriteAsync(buffer, 0, buffer.Length).ContinueWith((task) =>
{
stream.Close();
listener.Stop();
});
string error = context.Request.QueryString["error"];
if (error != null)
return;
string state = context.Request.QueryString["state"];
if (state != request.State)
return;
string code = context.Request.QueryString["code"];
State.Token = request.ExchangeCodeForAccessToken(code);
}
}
// state model
public class OAuthState : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private OAuthToken _token;
public OAuthToken Token
{
get => _token;
set
{
if (_token == value)
return;
_token = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Token)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSigned)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsNotSigned)));
}
}
public bool IsSigned => Token != null && Token.ExpirationDate > DateTime.Now;
public bool IsNotSigned => !IsSigned;
}
// This is a sample. Fille information (email, etc.) can depend on scopes
[DataContract]
public class OAuthToken
{
[DataMember(Name = "access_token")]
public string AccessToken { get; set; }
[DataMember(Name = "token_type")]
public string TokenType { get; set; }
[DataMember(Name = "expires_in")]
public int ExpiresIn { get; set; }
[DataMember(Name = "refresh_token")]
public string RefreshToken { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public string Email { get; set; }
[DataMember]
public string Picture { get; set; }
[DataMember]
public string Locale { get; set; }
[DataMember]
public string FamilyName { get; set; }
[DataMember]
public string GivenName { get; set; }
[DataMember]
public string Id { get; set; }
[DataMember]
public string Profile { get; set; }
[DataMember]
public string[] Scopes { get; set; }
// not from google's response, but we store this
public DateTime ExpirationDate { get; set; }
}
// largely inspired from
// https://github.com/googlesamples/oauth-apps-for-windows
public sealed class OAuthRequest
{
private const string ClientId = "My-Client-Id";
private const string ClientSecret = "My-Client-Secret";
private const string AuthorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth";
private const string TokenEndpoint = "https://www.googleapis.com/oauth2/v4/token";
private const string UserInfoEndpoint = "https://www.googleapis.com/oauth2/v3/userinfo";
private OAuthRequest()
{
}
public string AuthorizationRequestUri { get; private set; }
public string State { get; private set; }
public string RedirectUri { get; private set; }
public string CodeVerifier { get; private set; }
public string[] Scopes { get; private set; }
// https://developers.google.com/identity/protocols/OAuth2InstalledApp
public static OAuthRequest BuildLoopbackRequest(params string[] scopes)
{
var request = new OAuthRequest
{
CodeVerifier = RandomDataBase64Url(32),
Scopes = scopes
};
string codeChallenge = Base64UrlEncodeNoPadding(Sha256(request.CodeVerifier));
const string codeChallengeMethod = "S256";
string scope = BuildScopes(scopes);
request.RedirectUri = string.Format("http://{0}:{1}/", IPAddress.Loopback, GetRandomUnusedPort());
request.State = RandomDataBase64Url(32);
request.AuthorizationRequestUri = string.Format("{0}?response_type=code&scope=openid%20profile{6}&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}",
AuthorizationEndpoint,
Uri.EscapeDataString(request.RedirectUri),
ClientId,
request.State,
codeChallenge,
codeChallengeMethod,
scope);
return request;
}
// https://developers.google.com/identity/protocols/OAuth2InstalledApp Step 5: Exchange authorization code for refresh and access tokens
public OAuthToken ExchangeCodeForAccessToken(string code)
{
if (code == null)
throw new ArgumentNullException(nameof(code));
string tokenRequestBody = string.Format("code={0}&redirect_uri={1}&client_id={2}&code_verifier={3}&client_secret={4}&scope=&grant_type=authorization_code",
code,
Uri.EscapeDataString(RedirectUri),
ClientId,
CodeVerifier,
ClientSecret
);
return TokenRequest(tokenRequestBody, Scopes);
}
// this is not used in this sample, but can be used to refresh a token from an old one
// https://developers.google.com/identity/protocols/OAuth2InstalledApp Refreshing an access token
public OAuthToken Refresh(OAuthToken oldToken)
{
if (oldToken == null)
throw new ArgumentNullException(nameof(oldToken));
string tokenRequestBody = string.Format("refresh_token={0}&client_id={1}&client_secret={2}&grant_type=refresh_token",
oldToken.RefreshToken,
ClientId,
ClientSecret
);
return TokenRequest(tokenRequestBody, oldToken.Scopes);
}
private static T Deserialize<T>(string json)
{
if (string.IsNullOrWhiteSpace(json))
return default(T);
return Deserialize<T>(Encoding.UTF8.GetBytes(json));
}
private static T Deserialize<T>(byte[] json)
{
if (json == null || json.Length == 0)
return default(T);
using (var ms = new MemoryStream(json))
{
return Deserialize<T>(ms);
}
}
private static T Deserialize<T>(Stream json)
{
if (json == null)
return default(T);
var ser = CreateSerializer(typeof(T));
return (T)ser.ReadObject(json);
}
private static DataContractJsonSerializer CreateSerializer(Type type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
var settings = new DataContractJsonSerializerSettings
{
DateTimeFormat = new DateTimeFormat("yyyy-MM-dd'T'HH:mm:ss.fffK")
};
return new DataContractJsonSerializer(type, settings);
}
// https://stackoverflow.com/questions/223063/how-can-i-create-an-httplistener-class-on-a-random-port-in-c/
private static int GetRandomUnusedPort()
{
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop();
return port;
}
private static string RandomDataBase64Url(int length)
{
using (var rng = new RNGCryptoServiceProvider())
{
var bytes = new byte[length];
rng.GetBytes(bytes);
return Base64UrlEncodeNoPadding(bytes);
}
}
private static byte[] Sha256(string text)
{
using (var sha256 = new SHA256Managed())
{
return sha256.ComputeHash(Encoding.ASCII.GetBytes(text));
}
}
private static string Base64UrlEncodeNoPadding(byte[] buffer)
{
string b64 = Convert.ToBase64String(buffer);
// converts base64 to base64url.
b64 = b64.Replace('+', '-');
b64 = b64.Replace('/', '_');
// strips padding.
b64 = b64.Replace("=", "");
return b64;
}
private static OAuthToken TokenRequest(string tokenRequestBody, string[] scopes)
{
var request = (HttpWebRequest)WebRequest.Create(TokenEndpoint);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
byte[] bytes = Encoding.ASCII.GetBytes(tokenRequestBody);
using (var requestStream = request.GetRequestStream())
{
requestStream.Write(bytes, 0, bytes.Length);
}
var response = request.GetResponse();
using (var responseStream = response.GetResponseStream())
{
var token = Deserialize<OAuthToken>(responseStream);
token.ExpirationDate = DateTime.Now + new TimeSpan(0, 0, token.ExpiresIn);
var user = GetUserInfo(token.AccessToken);
token.Name = user.Name;
token.Picture = user.Picture;
token.Email = user.Email;
token.Locale = user.Locale;
token.FamilyName = user.FamilyName;
token.GivenName = user.GivenName;
token.Id = user.Id;
token.Profile = user.Profile;
token.Scopes = scopes;
return token;
}
}
private static UserInfo GetUserInfo(string accessToken)
{
var request = (HttpWebRequest)WebRequest.Create(UserInfoEndpoint);
request.Method = "GET";
request.Headers.Add(string.Format("Authorization: Bearer {0}", accessToken));
var response = request.GetResponse();
using (var stream = response.GetResponseStream())
{
return Deserialize<UserInfo>(stream);
}
}
private static string BuildScopes(string[] scopes)
{
string scope = null;
if (scopes != null)
{
foreach (var sc in scopes)
{
scope += "%20" + Uri.EscapeDataString(sc);
}
}
return scope;
}
// https://developers.google.com/+/web/api/rest/openidconnect/getOpenIdConnect
[DataContract]
private class UserInfo
{
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "kind")]
public string Kind { get; set; }
[DataMember(Name = "email")]
public string Email { get; set; }
[DataMember(Name = "picture")]
public string Picture { get; set; }
[DataMember(Name = "locale")]
public string Locale { get; set; }
[DataMember(Name = "family_name")]
public string FamilyName { get; set; }
[DataMember(Name = "given_name")]
public string GivenName { get; set; }
[DataMember(Name = "sub")]
public string Id { get; set; }
[DataMember(Name = "profile")]
public string Profile { get; set; }
[DataMember(Name = "gender")]
public string Gender { get; set; }
}
}
}
You are trying to login on a web view and Google blocks that.
Unfortunately, OAuth for desktop apps is tricky. I have some visual blog posts and a code sample you can run to understand behaviour:
Login by invoking the system browser
Receive the response by spinning up a loopback web server
Or receive the response via a private URI scheme (my preference)
My samples are coded in Electron / Javascript. However, the below C# code samples accompany the IdentityModel security library, and I would recommend using this library for your app:
Loopback Sample
Private Scheme Sample
You need to add this line :
settings.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 /CefSharp Browser" + Cef.CefSharpVersion;
after CefSettings settings = new CefSettings();
Google Blocked logins to it from embedded browser as an act against Man In The Middle Attacks.
I ran into an issue after finally getting my API call to work where it gives me the following exception
Unhandled Exception:
SQLite.SQLiteException: no such table: Token occurred
I have this class here
public class Token
{
[PrimaryKey]
public int Id { get; set; }
public string accessToken { get; set; }
public string errorDescription { get; set; }
public DateTime expireDate { get; set; }
public int expireIn { get; set; }
public Token() { }
}
here is my ios sqlite class for ios
public class SQLite_iOS : ISQLite
{
public SQLite_iOS() { }
public SQLite.SQLiteConnection GetConnection()
{
var dbName = "mydb.db3";
var documentPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
var libraryPath = Path.Combine(documentPath, "..", "Library");
var path = Path.Combine(libraryPath, dbName);
var connection = new SQLite.SQLiteConnection(path);
return connection;
}
}
here is my Api call
public async Task<string> LoginAsync(string email, string password)
{
var db = new SQLiteConnection(dbPath);
var tokenInfo = new Token();
var keyValues = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("email", email),
new KeyValuePair<string, string>("password", password),
new KeyValuePair<string, string>("grant_type", "password")
};
var request = new HttpRequestMessage(HttpMethod.Post, "https://myurl/v1/auth/login");
request.Content = new FormUrlEncodedContent(keyValues);
var client = new HttpClient();
var response = await client.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
JObject jwtDynamic = JsonConvert.DeserializeObject<dynamic>(content);
var accessTokenExpiration = jwtDynamic.Value<DateTime>(".expires");
var accessToken = jwtDynamic.Value<string>("token");
//Settings.AccessTokenExpirationDate = accessTokenExpiration;
Debug.WriteLine(accessTokenExpiration);
Debug.WriteLine(content);
tokenInfo = new Token();
tokenInfo.accessToken = "accessToken";
db.Insert(tokenInfo);
return accessToken;
}
I have been learning this as I go, and have learned a lot, but it's all new to me right now. Thanks for the help
Below are the steps that I've done to use RavenDB in client/server mode (not embedded) for my ASP.Net MVC application. Although I am following exactly the steps but the results are not as expected. Please correct me if there's any mistakes made.
Install RavenDB.Client & RavenDB.Server via Nuget.
Go to Packages folder, start Raven.Server.exe to get the service running
Open http://localhost:8080/ in browser, RavenStudio is up.
Created a database, say named as "testdb"
I have a RestaurantModel.cs.
internal class RestaurantModel{
public string ResName { get; set; }
public string ResAddress { get; set; }
public string ResCity { get; set; }
public string ResState { get; set; }
public int ResPostcode { get; set; }
public string ResPhoneNum { get; set; }
}
In my controller, I've initialized the document store, as well as opening the session.
public ActionResult Index()
{
using (var store = new DocumentStore
{
Url = "http://localhost:8080/",
DefaultDatabase = "testdb"
})
{
store.Initialize();
using (var session = store.OpenSession())
{
session.Store(new RestaurantModel
{
ResName = "TestName",
ResAddress = "Test Address",
ResCity = "TestCity",
ResState = "TestState",
ResPostcode = 82910,
ResPhoneNum = "02-28937481"
});
session.SaveChanges();
}
}
return View();
}
Build the solution. Refresh localhost:8080, data is still not inserted.
I have no idea what am I doing wrong although I'm following exactly all the tutorials I've gone through. So many attempts using different ways but still to no avail.
Thanks in advance for your help!
Tried hitting debug, it opens localhost:33062, but then it shows me server error as shown in below.
# To be more specific #
I have a RestaurantModel.cs
internal class RestaurantModel
{
public string ResName { get; set; }
public string ResAddress { get; set; }
public string ResCity { get; set; }
public string ResState { get; set; }
public int ResPostcode { get; set; }
public string ResPhoneNum { get; set; }
}
I have a AdminController
using FYP2.Models;
using Raven.Client.Document;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace FYP2.Controllers
{
public class AdminController : Controller
{
// GET: Admin
public ActionResult Index()
{
using (var store = new DocumentStore
{
Url = "http://localhost:8080/",
DefaultDatabase = "foodfurydb"
})
{
store.Initialize();
using (var session = store.OpenSession())
{
session.Store(new RestaurantModel
{
ResName = "Boxer Republic",
ResAddress = "NO 2A-G, Jalan BK 5A/2C",
ResCity = "Puchong",
ResState = "Selangor",
ResPostcode = 47180,
ResPhoneNum = "03-80748088"
});
session.SaveChanges();
}
}
return View();
}
public ActionResult AdminLogin()
{
return View();
}
public ActionResult AddRestaurant()
{
return View();
}
public ActionResult ManageFoodMenu()
{
return View();
}
public ActionResult ManageOrder()
{
return View();
}
public ActionResult ManageReservation()
{
return View();
}
}
}
I have Admin View, consist of
AddRestaurant, AdminLogin, ManageFoodMenu, ManageOrder, ManageReservation
I really don't know what can cause this problem. Just one question, you just compiled the project or you called your action domain:port/yourcontroller/index?
I've created a mvc project and copied your code:
public class HomeController : Controller
{
internal class RestaurantModel
{
public string ResName { get; set; }
public string ResAddress { get; set; }
public string ResCity { get; set; }
public string ResState { get; set; }
public int ResPostcode { get; set; }
public string ResPhoneNum { get; set; }
}
public ActionResult Index()
{
using (var store = new DocumentStore
{
Url = "http://locaslhost:8080/",
DefaultDatabase = "testdb"
})
{
store.Initialize();
using (var session = store.OpenSession())
{
session.Store(new RestaurantModel
{
ResName = "TestName",
ResAddress = "Test Address",
ResCity = "TestCity",
ResState = "TestState",
ResPostcode = 82910,
ResPhoneNum = "02-28937481"
});
session.SaveChanges();
}
}
return View();
}
}
When I accessed the path http://localhost:50791/ which corresponds to my HomeController/Index, everything went as expected:
Can you give more details about you are trying to do?
Is there a way to control the JSON output of JsonResult with attributes, similar to how you can use XmlElementAttribute and its bretheren to control the output of XML serialization?
For example, given the following class:
public class Foo
{
[SomeJsonSerializationAttribute("bar")]
public String Bar { get; set; }
[SomeJsonSerializationAttribute("oygevalt")]
public String Oygevalt { get; set; }
}
I'd like to then get the following output:
{ bar: '', oygevalt: '' }
As opposed to:
{ Bar: '', Oygevalt: '' }
I wanted something a bit more baked into the framework than what Jarrett suggested, so here's what I did:
JsonDataContractActionResult:
public class JsonDataContractActionResult : ActionResult
{
public JsonDataContractActionResult(Object data)
{
this.Data = data;
}
public Object Data { get; private set; }
public override void ExecuteResult(ControllerContext context)
{
var serializer = new DataContractJsonSerializer(this.Data.GetType());
String output = String.Empty;
using (var ms = new MemoryStream())
{
serializer.WriteObject(ms, this.Data);
output = Encoding.Default.GetString(ms.ToArray());
}
context.HttpContext.Response.ContentType = "application/json";
context.HttpContext.Response.Write(output);
}
}
JsonContract() method, added to my base controller class:
public ActionResult JsonContract(Object data)
{
return new JsonDataContractActionResult(data);
}
Sample Usage:
public ActionResult Update(String id, [Bind(Exclude="Id")] Advertiser advertiser)
{
Int32 advertiserId;
if (Int32.TryParse(id, out advertiserId))
{
// update
}
else
{
// insert
}
return JsonContract(advertiser);
}
Note: If you're looking for something more performant than JsonDataContractSerializer, you can do the same thing using JSON.NET instead. While JSON.NET doesn't appear to utilize DataMemberAttribute, it does have its own JsonPropertyAttribute which can be used to accomplish the same thing.
Here's my implementation of Daniel Schaffer's answer, with the suggested improvements by Justin Rusbatch and Daniel incorporated.
using System;
using System.Runtime.Serialization.Json;
using System.Web.Mvc;
public class JsonDataContractActionResult : JsonResult
{
public JsonDataContractActionResult( Object data )
{
this.Data = data;
}
public override void ExecuteResult( ControllerContext context )
{
var serializer = new DataContractJsonSerializer( this.Data.GetType() );
context.HttpContext.Response.ContentType = "application/json";
serializer.WriteObject( context.HttpContext.Response.OutputStream,
this.Data );
}
}
This is the solution to use NewtonSoft Json.Net (for performance)
I've found part of the solution here and on SO
public class JsonNetResult : ActionResult
{
public Encoding ContentEncoding { get; set; }
public string ContentType { get; set; }
public object Data { get; set; }
public JsonSerializerSettings SerializerSettings { get; set; }
public Formatting Formatting { get; set; }
public JsonNetResult(object data, Formatting formatting)
: this(data)
{
Formatting = formatting;
}
public JsonNetResult(object data):this()
{
Data = data;
}
public JsonNetResult()
{
Formatting = Formatting.None;
SerializerSettings = new JsonSerializerSettings();
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
throw new ArgumentNullException("context");
var response = context.HttpContext.Response;
response.ContentType = !string.IsNullOrEmpty(ContentType)
? ContentType
: "application/json";
if (ContentEncoding != null)
response.ContentEncoding = ContentEncoding;
if (Data == null) return;
var writer = new JsonTextWriter(response.Output) { Formatting = Formatting };
var serializer = JsonSerializer.Create(SerializerSettings);
serializer.Serialize(writer, Data);
writer.Flush();
}
}
So that in my controller, I can do that
return new JsonNetResult(result);
In my model, I can now have:
[JsonProperty(PropertyName = "n")]
public string Name { get; set; }
Note that now, you have to set the JsonPropertyAttribute to every property you want to serialize.
I know this is an old question but for those looking for just how to avoid properties from being serialized use the ScriptIgnoreAttribute in the namespace System.Web.Script.Serialization. Sadly still can't controll the name of the serialized properties but somebody might find this helpfull.
public class MyClass {
[ScriptIgnoreAttribute]
public bool PropertyNotSerialized { get; set; }
public bool AnyProperty { get; set; }
}
Will output as Json result the following:
{"AnyProperty ": false}
Easy answer: the DataContractJsonSerializer should respect the [DataContract] and [DataMember] attributes in the System.Runtime.Serialization namespace of the BCL.
These answers were helpful to me, but coming to this problem a few years later than everyone else I found that this code didn't work with the current framework version. This version works with Newtonsoft.Json and ASP NET Core 3.1:
/*
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
*/
public class JsonDataContractActionResult : IActionResult
{
public JsonDataContractActionResult(object data)
{
this.Data = data;
}
public object Data { get; private set; }
public async Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.ContentType = "application/json";
JsonSerializer serializer = new JsonSerializer();
serializer.NullValueHandling = NullValueHandling.Ignore;
using (MemoryStream ms = new MemoryStream()) {
using (StreamWriter sw = new StreamWriter(ms))
{
using (JsonWriter writer = new JsonTextWriter(sw))
{
serializer.Serialize(writer, Data);
}
}
byte[] b = ms.ToArray();
await context.HttpContext.Response.Body.WriteAsync(b);
}
}
}