I need to combine these two Data. They both have their own Fragment,Dao, Model and Repository. And both return different data from different tables.
ItemFavourite table stores id of the tables aboves Item and ItemMoto.
public LiveData<Resource<List<Item>>> getItemFavouriteData() {
return itemFavouriteData;
}
//Moto
public LiveData<Resource<List<ItemMoto>>> getItemFavouriteDataMoto() {
return itemFavouriteDataMoto;
}
This is how I tried it.
public class FavouriteViewModel extends PSViewModel {
private final LiveData<Resource<List<Item>>> itemFavouriteData;
private final LiveData<Resource<List<ItemMoto>>> itemFavouriteDataMoto;
private MutableLiveData<FavouriteViewModel.TmpDataHolder> itemFavouriteListObj = new
MutableLiveData<>();
private MutableLiveData<FavouriteMotoViewModel.TmpDataHolder> itemFavouriteListObjMoto = new
MutableLiveData<>();
#Inject
FavouriteViewModel(ItemRepository itemRepository, ItemMotoRepository itemMotoRepository) {
itemFavouriteData = Transformations.switchMap(itemFavouriteListObj, obj -> {
if (obj == null) {
return AbsentLiveData.create();
}
Utils.psLog("itemFavouriteData");
return itemRepository.getFavouriteList(Config.API_KEY, obj.userId, obj.offset);
});
itemFavouriteDataMoto = Transformations.switchMap(itemFavouriteListObjMoto, obj -> {
if (obj == null) {
return AbsentLiveData.create();
}
Utils.psLog("itemFavouriteData");
return itemMotoRepository.getFavouriteList(Config.API_KEY, obj.userId, obj.offset);
});
}
public LiveData<Resource<List<Item>>> getItemFavouriteData() {
return itemFavouriteData;
}
public LiveData<Resource<List<ItemMoto>>> getItemFavouriteDataMoto() {
return itemFavouriteDataMoto;
}
private static LiveData<Resource<List<Item>>> mergeDataSources(LiveData... sources) {
MediatorLiveData<Resource<List<Item>>> mergedSources = new MediatorLiveData();
for (LiveData source : sources) {
mergedSources.addSource(source, mergedSources::setValue);
}
return mergedSources;
}
public LiveData<Resource<List<Item>>> getFavourites() {
return mergeDataSources(
getItemFavouriteDataMoto(),
getItemFavouriteData());
}
}
From Fragment I observe the data like this:
LiveData<Resource<List<Item>>> news = favouriteViewModel.getFavourites();
if (news != null) {
news.observe(this, listResource -> {
if (listResource != null) {
switch (listResource.status) {
case LOADING:
// Loading State
// Data are from Local DB
if (listResource.data != null) {
//fadeIn Animation
fadeIn(binding.get().getRoot());
// Update the data
replaceData(listResource.data);
}
break;
case SUCCESS:
// Success State
// Data are from Server
if (listResource.data != null) {
// Update the data
replaceData(listResource.data);
}
favouriteViewModel.setLoadingState(false);
break;
case ERROR:
// Error State
favouriteViewModel.setLoadingState(false);
favouriteViewModel.forceEndLoading = true;
break;
default:
// Default
break;
}
} else {
// Init Object or Empty Data
if (favouriteViewModel.offset > 1) {
// No more data for this list
// So, Block all future loading
favouriteViewModel.forceEndLoading = true;
}
}
});
}
The only data I am getting are from Item table only.
Using mediator live data we can observe the 2 livedata.
val groupChatFeed: LiveData<List<Feed<*>>> = MediatorLiveData<List<Feed<*>>>().apply {
fun prepareDataAndSetStates(): List<Feed<*>> {
val data: MutableList<Feed<*>> = mutableListOf()
if (postList.value?.data?.isNullOrEmpty() == false) {
data.addAll(postList.value?.data ?: emptyList())
}
if (connectionRecommendations.value?.data?.isNullOrEmpty() == false) {
val recommendations = connectionRecommendations.value?.data?.toFeedItem()
data.add(recommendations)
}
return data
}
addSource(postList) {
value = prepareDataAndSetStates()
}
addSource(connectionRecommendations) {
value = prepareDataAndSetStates()
}
}
We are observing 2 different livedata postList and connectionRecommendations.
You can use MediatorLiveData and tuples, but you can technically also use this library I wrote for this specific purpose which does it for you, and solve it like this
import static com.zhuinden.livedatacombineutiljava.LiveDataCombineUtil.*;
private final LiveData<Pair<Resource<List<Item>>, Resource<List<ItemMoto>>>> favorites = combine(itemFavouriteData, itemFavouriteDataMoto, (favorites, favoriteMoto) -> {
return Pair.create(favorites, favoriteMoto);
});
public LiveData<Pair<Resource<List<Item>>, Resource<List<ItemMoto>>>> getFavorites() {
return favorites;
}
Related
I am loading data from an API into an adapter, when the user clicks on it, it downloads using DownloadManager, i then use a broadcaster to let my activity know the downloadId and the hyperlink (the unique identifer for room).
I have so far been unable to figure out how best to use the same observer as initially this will just be getting the data (which has no downloadId) and then later passing through the downloadId and hyperlink to the repository. I have been able to do this successfully from the repository as hardcoded data so far.
My ViewModel:
#Inject
ItemViewModel(#NonNull ItemRepository itemRepository){
items = Transformations.switchMap(query, search -> {
if (search == null){
return AbsentLiveData.create();
}
// Transformations.switchMap(downloadable, inner -> {
// itemRepository.getDBItems(search, inner.getHyperlink(), inner.getDownloadId());
// });
return itemRepository.getDBItems(search, null, 0);
});
As I can't get the data from downloadable without doing switchMap, and I am unable to get itemRepository.getDBItems without it returning, I am stuck.
My broadcast result:
#Override
public void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == DOWNLOAD_ID){
Item i = new Item();
i.setHyperlink(resultData.getString("hyperlink"));
i.setDownloadId(resultData.getLong("downloadId"));
itemViewModel.setDownloadable(i);
}
}
Reviewed Google samples and made an object to wrap around it inside the ViewModel.
My end result:
#Inject
ItemViewModel(#NonNull ItemRepository itemRepository){
this.itemQuery = new MutableLiveData<>();
items = Transformations.switchMap(itemQuery, input -> {
if (input.isEmpty()){
return AbsentLiveData.create();
}
return itemRepository.getDBItems(input.query, input.hyperlink, input.downloadId);
});
#VisibleForTesting
public void setItemQuery(String query, String hyperlink, long downloadId) {
ItemQuery update = new ItemQuery(query,hyperlink,downloadId);
if (Objects.equals(itemQuery.getValue(), update)) {
return;
}
itemQuery.setValue(update);
}
#VisibleForTesting
static class ItemQuery{
public final String query;
public final String hyperlink;
public final long downloadId;
ItemQuery(String query, String hyperlink, long downloadId) {
this.query = query;
this.hyperlink = hyperlink;
this.downloadId = downloadId;
}
boolean isEmpty() {
return query == null;
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ItemQuery itemQuery = (ItemQuery) o;
if (query != null ? !query.equals(itemQuery.query) : itemQuery.query != null) {
return false;
}
return hyperlink != null ? hyperlink.equals(itemQuery.hyperlink) : itemQuery.hyperlink == null;
}
}
Seems to work for my intended purposes.
i have a fragment in my app that i show two list of saparate data in it.i'm using from android architecture components to load my data.
Once the data is fetched from the network, I store it locally using Room DB and then display it on the UI using ViewModel that observes on the LiveData object (this works fine). However, I want to be able to have a refreshLayout which When Refreshing Occurs a refresh action and perform a network request to get new data from the API if and only if there is a network connection.The issue is when Refreshing Occurs data load from locate database and network together .
my question is :How do I manage to get data only from Network when refreshing data?
How do I manage to get data only from Network when refreshing data?
I've seen this question and it didn't help me...
my codes:
repository:
public NetworkResult<LiveData<HomeHealthModel>> getHomeHealth(String query) {
MutableLiveData<String> _liveError = new MutableLiveData<>();
MutableLiveData<HomeHealthModel> data = new MutableLiveData<>();
LiveData<List<GeneralItemModel>> liveClinics = App.getDatabase().getGeneralItemDAO().getTops(GeneralItemType.Clinics, GeneralItemType.TOP);
LiveData<List<GeneralItemModel>> liveDoctors = App.getDatabase().getGeneralItemDAO().getTops(GeneralItemType.Doctors, GeneralItemType.TOP);
setupService(_liveError); //request data from network
data.postValue(new HomeHealthModel(liveClinics, liveDoctors));
_liveError.postValue(String.valueOf(NetworkResponseType.LocaleData));
return new NetworkResult<>(_liveError, data);
}
my viewModel
public class HomeHealthVM extends ViewModel {
private MutableLiveData<String> queryLiveData;
private LiveData<String> networkErrors;
private LiveData<List<GeneralItemModel>> Clinics;
private LiveData<List<GeneralItemModel>> Doctors;
public HomeHealthVM(HealthRepository repository) {
queryLiveData = new MutableLiveData<>();
LiveData<NetworkResult<LiveData<HomeHealthModel>>> repoResult;
repoResult = Transformations.map(queryLiveData, repository::getHomeHealth);
LiveData<HomeHealthModel> model = Transformations.switchMap(repoResult, input -> input.data);
Doctors = Transformations.switchMap(model, HomeHealthModel::getDoctors);
Clinics = Transformations.switchMap(model, HomeHealthModel::getClinics);
networkErrors = Transformations.switchMap(repoResult, input -> input.error);
}
public void search(String queryString) {
queryLiveData.postValue(queryString);
}
public String lastQueryValue() {
return queryLiveData.getValue();
}
public LiveData<String> getNetworkErrors() {
return networkErrors;
}
public LiveData<List<GeneralItemModel>> getClinics() {
return Clinics;
}
public LiveData<List<GeneralItemModel>> getDoctors() {
return Doctors;
}
}
my fragment code:
private void setupViewModel() {
ViewModelFactory<HealthRepository> factory = new ViewModelFactory<>(new HealthRepository());
healthVM = ViewModelProviders.of(this, factory).get(HomeHealthVM.class);
healthVM.getNetworkErrors().observe(this, states -> {
try {
if (Integer.parseInt(states) != WarningDialogType.Success &&
Integer.parseInt(states) != WarningDialogType.Locale) {
stopLoading();
linerNoInternet.setVisibility(View.VISIBLE);
linerContent.setVisibility(View.GONE);
}
} catch (Exception e) {
stopLoading();
linerNoInternet.setVisibility(View.VISIBLE);
linerContent.setVisibility(View.GONE);
}
});
healthVM.getDoctors().observe(this, doctors -> {
if (doctors.size() > 0) {
doctorsAdapter.submitList(doctors);
stopLoading();
} else {
}
});
healthVM.getClinics().observe(this, clinics -> {
if (clinics.size() > 0) {
clinicsAdapter.submitList(clinics);
stopLoading();
} else {
conesClinics.setVisibility(View.GONE);
}
});
healthVM.search("");
}
I am using Retrofit, Live data. There is one situation on my project, I have to make sequence of network call. if any one fails it should return error.
At present I have two live data observers to get the work done, which is not good approach so I wanted to know the better approach or sample code to handle such requirement.
Note: I am not using Rxjava.
View code Basic logic
String id = "items/1233"; //ID which has to to be deleted
if (isCustomizedItem) {
viewModel.deleteEvent(id);
} else {
viewModel.createCustomItems();
viewModel.deleteEvent(id);
}
Livedata observers
viewModel.getItemDeleted().observe(this, serverResponse -> {
if (serverResponse.status == Status.SUCCESS) {
Timber.i("Successfully deleted");
}
});
viewModel.itemCreated().observe(this, serverResponse -> {
if (serverResponse.status == Status.SUCCESS) {
Timber.i("new items added");
//Again call delete for specific item
viewModel.deleteEvent(id);
}
});
Viewmodel code
createItems = Transformations.switchMap(eventData, (data) -> {
if (canCreateItems(data)) {
return AbsentLiveData.create();
} else {
return eventItemRepository.createItems();
}
});
deleteItem = Transformations.switchMap(deleteItem, (item) -> {
if (!isValidItem(item)) {
return AbsentLiveData.create();
} else {
return eventItemRepository.deleteItem(item);
}
});
Repo code.
public LiveData<Resource<List<Items>>> createItems() {
return new NetworkBoundResource<List<Items>> (executors) {
#NonNull
#Override
protected LiveData<ApiResponse<List<Items>>> createCall() {
return services.createItems();
}
}.asLiveData();
}
public LiveData<Resource<EmptyResponse>> deleteItem(String id) {
return new NetworkBoundResource<EmptyResponse> (executors) {
#NonNull
#Override
protected LiveData<ApiResponse<EmptyResponse>> createCall() {
return services.deleteItem(id);
}
}.asLiveData();
}
Service interface.
#GET(Constants.API_PATH+"/createitems/")
LiveData<ApiResponse<List<Items>>> createItems();
#GET(Constants.API_PATH+"/delete/{id}")
LiveData<ApiResponse<EmptyResponse>> deleteItem(#Path("id") String id);
I want to call createItems and deleteItem together. How can i achieve this?
Finally I write the solution. I used Mediatorlivedata to observe livedata changes on viewmodel.
Method which is responsible for both network call
public LiveData<Resource<EmptyResponse>> updateEvent(RequestCustomEvent request) {
return new UpdateItineraryRequests<EmptyResponse>(request).asLiveData();
}
and a class which will observe live data changes on viewmodel.
private class UpdateItineraryRequests<RequestType> {
private final MediatorLiveData<Resource<RequestType>> result = new MediatorLiveData<>();
UpdateItineraryRequests(RequestCustomEvent request) {
startExecution(request);
}
void startExecution(RequestCustomEvent request) {
//First check the its custom or not if its custom then directly change.
if (request.isCustom()) {
LiveData<Resource<EmptyResponse>> observable = repo.deleteItem(request.getEventID());
result.addSource(observable, response -> {
result.removeSource(observable);
if (response.status == Status.SUCCESS) {
result.setValue(Resource.success(null));
} else {
result.setValue(Resource.error("unable to delete", null));
}
});
} else {
LiveData<Resource<List<Items>>> itemsObservable = repo.createItems(request.getDataToChange());
result.addSource(itemsObservable, response -> {
result.removeSource(itemsObservable);
LiveData<Resource<EmptyResponse>> observable = repo.deleteItem(request.getEventID());
result.addSource(observable, response -> {
result.removeSource(observable);
if (response.status == Status.SUCCESS) {
//Do rest of network calls
}
}
});
}
}
LiveData<Resource<RequestType>> asLiveData() {
return result;
}
}
I'm trying load posts from blog. I use mosby + retrofit + rxjava.
public class PostRepository implements IPostRepository {
private Api api;
private long last_id = 0;
private Single<List<Post>> postList;
public PostRepository(Api api) {
this.api = api;
}
#Override
public Single<List<Post>> getList() {
this.load();
return postList;
}
private void load() {
Single<List<Post>> tmp;
Log.d(Configuration.DEBUG_TAG, "Loading " + last_id);
tmp = api.getPostList(last_id)
.map(posts -> {
ArrayList<Post> postList = new ArrayList<>();
for (PostResponse post : posts) {
if (last_id == 0 || last_id > post.id) {
last_id = post.id;
}
postList.add(new Post(
post.id,
post.thumb,
post.created_at,
post.title
));
}
return postList;
});
if (postList == null) {
postList = tmp;
} else {
postList.mergeWith(tmp);
}
}
#Override
public Single<Post> getDetail(long id) {
return api.getPost(id)
.map(postResponse -> new Post(
postResponse.id,
postResponse.thumb,
postResponse.created_at,
postResponse.title,
postResponse.body
));
}
}
and api
public interface Api {
#GET("posts")
Single<PostListResponse> getPostList(#Query("last_id") long last_id);
#GET("post/{id}")
Single<PostResponse> getPost(#Path("id") long id);
}
First query to website is ok. https://site/posts?last_id=0
But second run function getList does not work.
I always get the same get query with last_id = 0, but line in console write
D/App: Loading 1416
D/App: 1416
D/OkHttp: --> GET https://site/posts?last_id=0 http/1.1
if i write
tmp = api.getPostList(1000)
then i get true query string https://site/posts?last_id=1000
Update
I rewrite code repository.
public class PostRepository implements IPostRepository {
private Api api;
private long last_id = 0;
private List<Post> postList = new ArrayList<>();
private Observable<List<Post>> o;
public PostRepository(Api api) {
this.api = api;
}
#Override
public Single<List<Post>> getList() {
return load();
}
private Single<List<Post>> load() {
return api.getPostList(last_id)
.map(posts -> {
for (PostResponse post : posts) {
if (last_id == 0 || last_id > post.id) {
last_id = post.id;
}
postList.add(new Post(
post.id,
post.thumb,
post.created_at,
post.title
));
}
return postList;
});
}
#Override
public Single<Post> getDetail(long id) {
return api.getPost(id)
.map(postResponse -> new Post(
postResponse.id,
postResponse.thumb,
postResponse.created_at,
postResponse.title,
postResponse.body
));
}
}
It's work
Your problem lies in this code fragment:
if (postList == null) {
postList = tmp;
} else {
postList.mergeWith(tmp); // here
}
Operators on observables are performing immutable operations, which means that it always returns new stream which is a modified version of the previous one. That means, that when you apply mergeWith operator, the result of this is thrown away as you are not storing it anywhere. The most easy to fix this is to replace the old postList variable with the new stream.
However, this is not optimal way of doing this. You should have a look on Subjects and emit new values within the old stream as your current solution will not affect previous subscribers as they have subscribed to a different stream
I am developing a messaging application for Android and I am using an Azure Web API as my backend which connects to an Azure SQL DB. I am trying to get the service to allow users to log in and proceed through the application with an identity. I want to use Individual User Accounts to do this but I do not know how.
I have been through every piece of documentation on the internet and yet I am still at a loss on how to receive a token from the service and use it to access auhtorised resources.
If someone could please highlight the appropriate methods I need to call from the generated classes as well as methods I should write myself, that would be very helpful!
This is the Account Controller that was generated:
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OAuth;
using AcademicAssistant.Models;
using AcademicAssistant.Providers;
using AcademicAssistant.Results;
namespace AcademicAssistant.Controllers
{
[Authorize]
[RoutePrefix("api/Account")]
public class AccountController : ApiController
{
private const string LocalLoginProvider = "Local";
private ApplicationUserManager _userManager;
public AccountController()
{
}
public AccountController(ApplicationUserManager userManager,
ISecureDataFormat<AuthenticationTicket> accessTokenFormat)
{
UserManager = userManager;
AccessTokenFormat = accessTokenFormat;
}
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
public ISecureDataFormat<AuthenticationTicket> AccessTokenFormat { get; private set; }
// GET api/Account/UserInfo
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("UserInfo")]
public UserInfoViewModel GetUserInfo()
{
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
return new UserInfoViewModel
{
Email = User.Identity.GetUserName(),
HasRegistered = externalLogin == null,
LoginProvider = externalLogin != null ? externalLogin.LoginProvider : null
};
}
// POST api/Account/Logout
[Route("Logout")]
public IHttpActionResult Logout()
{
Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
return Ok();
}
// GET api/Account/ManageInfo?returnUrl=%2F&generateState=true
[Route("ManageInfo")]
public async Task<ManageInfoViewModel> GetManageInfo(string returnUrl, bool generateState = false)
{
IdentityUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user == null)
{
return null;
}
List<UserLoginInfoViewModel> logins = new List<UserLoginInfoViewModel>();
foreach (IdentityUserLogin linkedAccount in user.Logins)
{
logins.Add(new UserLoginInfoViewModel
{
LoginProvider = linkedAccount.LoginProvider,
ProviderKey = linkedAccount.ProviderKey
});
}
if (user.PasswordHash != null)
{
logins.Add(new UserLoginInfoViewModel
{
LoginProvider = LocalLoginProvider,
ProviderKey = user.UserName,
});
}
return new ManageInfoViewModel
{
LocalLoginProvider = LocalLoginProvider,
Email = user.UserName,
Logins = logins,
ExternalLoginProviders = GetExternalLogins(returnUrl, generateState)
};
}
// POST api/Account/ChangePassword
[Route("ChangePassword")]
public async Task<IHttpActionResult> ChangePassword(ChangePasswordBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IdentityResult result = await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword,
model.NewPassword);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
// POST api/Account/SetPassword
[Route("SetPassword")]
public async Task<IHttpActionResult> SetPassword(SetPasswordBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IdentityResult result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
// POST api/Account/AddExternalLogin
[Route("AddExternalLogin")]
public async Task<IHttpActionResult> AddExternalLogin(AddExternalLoginBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
AuthenticationTicket ticket = AccessTokenFormat.Unprotect(model.ExternalAccessToken);
if (ticket == null || ticket.Identity == null || (ticket.Properties != null
&& ticket.Properties.ExpiresUtc.HasValue
&& ticket.Properties.ExpiresUtc.Value < DateTimeOffset.UtcNow))
{
return BadRequest("External login failure.");
}
ExternalLoginData externalData = ExternalLoginData.FromIdentity(ticket.Identity);
if (externalData == null)
{
return BadRequest("The external login is already associated with an account.");
}
IdentityResult result = await UserManager.AddLoginAsync(User.Identity.GetUserId(),
new UserLoginInfo(externalData.LoginProvider, externalData.ProviderKey));
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
// POST api/Account/RemoveLogin
[Route("RemoveLogin")]
public async Task<IHttpActionResult> RemoveLogin(RemoveLoginBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IdentityResult result;
if (model.LoginProvider == LocalLoginProvider)
{
result = await UserManager.RemovePasswordAsync(User.Identity.GetUserId());
}
else
{
result = await UserManager.RemoveLoginAsync(User.Identity.GetUserId(),
new UserLoginInfo(model.LoginProvider, model.ProviderKey));
}
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
// GET api/Account/ExternalLogin
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
[AllowAnonymous]
[Route("ExternalLogin", Name = "ExternalLogin")]
public async Task<IHttpActionResult> GetExternalLogin(string provider, string error = null)
{
if (error != null)
{
return Redirect(Url.Content("~/") + "#error=" + Uri.EscapeDataString(error));
}
if (!User.Identity.IsAuthenticated)
{
return new ChallengeResult(provider, this);
}
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
if (externalLogin.LoginProvider != provider)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
return new ChallengeResult(provider, this);
}
ApplicationUser user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,
externalLogin.ProviderKey));
bool hasRegistered = user != null;
if (hasRegistered)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
}
else
{
IEnumerable<Claim> claims = externalLogin.GetClaims();
ClaimsIdentity identity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
Authentication.SignIn(identity);
}
return Ok();
}
// GET api/Account/ExternalLogins?returnUrl=%2F&generateState=true
[AllowAnonymous]
[Route("ExternalLogins")]
public IEnumerable<ExternalLoginViewModel> GetExternalLogins(string returnUrl, bool generateState = false)
{
IEnumerable<AuthenticationDescription> descriptions = Authentication.GetExternalAuthenticationTypes();
List<ExternalLoginViewModel> logins = new List<ExternalLoginViewModel>();
string state;
if (generateState)
{
const int strengthInBits = 256;
state = RandomOAuthStateGenerator.Generate(strengthInBits);
}
else
{
state = null;
}
foreach (AuthenticationDescription description in descriptions)
{
ExternalLoginViewModel login = new ExternalLoginViewModel
{
Name = description.Caption,
Url = Url.Route("ExternalLogin", new
{
provider = description.AuthenticationType,
response_type = "token",
client_id = Startup.PublicClientId,
redirect_uri = new Uri(Request.RequestUri, returnUrl).AbsoluteUri,
state = state
}),
State = state
};
logins.Add(login);
}
return logins;
}
// POST api/Account/Register
[AllowAnonymous]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
// POST api/Account/RegisterExternal
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("RegisterExternal")]
public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var info = await Authentication.GetExternalLoginInfoAsync();
if (info == null)
{
return InternalServerError();
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
protected override void Dispose(bool disposing)
{
if (disposing && _userManager != null)
{
_userManager.Dispose();
_userManager = null;
}
base.Dispose(disposing);
}
#region Helpers
private IAuthenticationManager Authentication
{
get { return Request.GetOwinContext().Authentication; }
}
private IHttpActionResult GetErrorResult(IdentityResult result)
{
if (result == null)
{
return InternalServerError();
}
if (!result.Succeeded)
{
if (result.Errors != null)
{
foreach (string error in result.Errors)
{
ModelState.AddModelError("", error);
}
}
if (ModelState.IsValid)
{
// No ModelState errors are available to send, so just return an empty BadRequest.
return BadRequest();
}
return BadRequest(ModelState);
}
return null;
}
private class ExternalLoginData
{
public string LoginProvider { get; set; }
public string ProviderKey { get; set; }
public string UserName { get; set; }
public IList<Claim> GetClaims()
{
IList<Claim> claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, ProviderKey, null, LoginProvider));
if (UserName != null)
{
claims.Add(new Claim(ClaimTypes.Name, UserName, null, LoginProvider));
}
return claims;
}
public static ExternalLoginData FromIdentity(ClaimsIdentity identity)
{
if (identity == null)
{
return null;
}
Claim providerKeyClaim = identity.FindFirst(ClaimTypes.NameIdentifier);
if (providerKeyClaim == null || String.IsNullOrEmpty(providerKeyClaim.Issuer)
|| String.IsNullOrEmpty(providerKeyClaim.Value))
{
return null;
}
if (providerKeyClaim.Issuer == ClaimsIdentity.DefaultIssuer)
{
return null;
}
return new ExternalLoginData
{
LoginProvider = providerKeyClaim.Issuer,
ProviderKey = providerKeyClaim.Value,
UserName = identity.FindFirstValue(ClaimTypes.Name)
};
}
}
private static class RandomOAuthStateGenerator
{
private static RandomNumberGenerator _random = new RNGCryptoServiceProvider();
public static string Generate(int strengthInBits)
{
const int bitsPerByte = 8;
if (strengthInBits % bitsPerByte != 0)
{
throw new ArgumentException("strengthInBits must be evenly divisible by 8.", "strengthInBits");
}
int strengthInBytes = strengthInBits / bitsPerByte;
byte[] data = new byte[strengthInBytes];
_random.GetBytes(data);
return HttpServerUtility.UrlTokenEncode(data);
}
}
#endregion
}
}
This is the message controller I wrote:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description;
using AcademicAssistant.Models;
namespace AcademicAssistant.Controllers
{
[Authorize]
public class MessagesController : ApiController
{
private AcademicAssistantContext db = new AcademicAssistantContext();
// GET: api/Messages
[Authorize]
public IQueryable<Message> GetMessages()
{
return db.Messages;
}
// GET: api/Messages/5
[Authorize]
[ResponseType(typeof(Message))]
public async Task<IHttpActionResult> GetMessage(int id)
{
Message message = await db.Messages.FindAsync(id);
if (message == null)
{
return NotFound();
}
return Ok(message);
}
// PUT: api/Messages/5
[Authorize]
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutMessage(int id, Message message)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != message.MessageID)
{
return BadRequest();
}
db.Entry(message).State = EntityState.Modified;
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MessageExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
// POST: api/Messages
[Authorize]
[ResponseType(typeof(Message))]
public async Task<IHttpActionResult> PostMessage(Message message)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Messages.Add(message);
await db.SaveChangesAsync();
return CreatedAtRoute("DefaultApi", new { id = message.MessageID }, message);
}
// DELETE: api/Messages/5
[Authorize]
[ResponseType(typeof(Message))]
public async Task<IHttpActionResult> DeleteMessage(int id)
{
Message message = await db.Messages.FindAsync(id);
if (message == null)
{
return NotFound();
}
db.Messages.Remove(message);
await db.SaveChangesAsync();
return Ok(message);
}
[Authorize]
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
[Authorize]
private bool MessageExists(int id)
{
return db.Messages.Count(e => e.MessageID == id) > 0;
}
}
}