Breeze OData: Get single entry with GetEntityByKey (EntitySetController) - asp.net-mvc-4

Reading the documentation in Breeze website, to retrieve a single entity have to use the fetchEntityByKey.
manager.fetchEntityByKey(typeName, id, true)
.then(successFn)
.fail(failFn)
Problem 1: Metadata
When trying to use this method, an error is displayed because the metadata has not yet been loaded. More details about the error here.
The result is that whenever I need to retrieve a single entity, have to check if the metadata is loaded.
manager = new breeze.EntityManager(serviceName);
successFn = function(xhr) {}
failFn = function(xhr) {};
executeQueryFn = function() {
return manager.fetchEntityByKey(typeName, id, true).then(successFn).fail(failFn);
};
if (manager.metadataStore.isEmpty()) {
return manager.fetchMetadata().then(executeQueryFn).fail(failFn);
} else {
return executeQueryFn();
}
Question
How can I extend the breeze, creating a Get method to check if metadata is loaded, and if not, load it?
Problem 2: OData and EntitySetController
I would use the OData standard (with EntitySetController) in my APIs.
This page in Breeze documentation shows how, then follow this tutorial to deploy my app with OData.
The problem as you can see here and here, is that the EntitySetController follows the odata pattern, to retrieve an entity must use odata/entity(id), or to retrieve all entities you can use `odata/entity'.
Example
In controller:
[BreezeController]
public class passosController : EntitySetController<Passo>
{
[HttpGet]
public string Metadata()
{
return ContextProvider.Metadata();
}
[HttpGet, Queryable(AllowedQueryOptions = AllowedQueryOptions.All, PageSize = 20)]
public override IQueryable<T> Get()
{
return Repositorio.All();
}
[HttpGet]
protected override T GetEntityByKey(int key)
{
return Repositorio.Get(key);
}
}
When I use:
manager = new breeze.EntityManager("/odata/passos");
manager.fetchEntityByKey("Passo", 1, true)
.then(successFn)
.fail(failFn)
The url generated is: /odata/passos/Passos?$filter=Id eq 1
The correct should be: /odata/passos(2)
Question
How can I modify Breeze for when use fetchEntityByKey to retrieve entity use odata/entity(id)?

Related

EF Core not setting class variables automatically

I want to switch my code to an async implementation. When I want to do this then I notice that my related data gets not set automatically after I retrieve them like it used to do it.
This is the initial function that gets called from an API controller. I used the AddDbContext function to add the dbcontext class via dependency injection into my controller:
public async Task<Application> GetApplicationById(AntragDBNoInheritanceContext dbContext, int id)
{
List<Application> ApplicationList = await dbContext.Applications.FromSqlRaw("Exec dbo.GetApplication {0}", id).ToListAsync();
Application Application = ApplicationList.First();
if(Application != null)
{
await CategoryFunctions.GetCategoryByApplicationID(Application.Id);
}
}
The GetCategoryByApplicationId function loads the related category of an application which is a many to one relation between Category and Application:
public async Task<Category> GetCategoryByApplicationID(int applicationID)
{
var optionsBuilder = new DbContextOptionsBuilder<AntragDBNoInheritanceContext>();
optionsBuilder.UseSqlServer(ApplicationDBConnection.APPLICATION_CONNECTION);
using (var dbContext = new AntragDBNoInheritanceContext(optionsBuilder.Options))
{
List<Category> category = await dbContext.Categories.FromSqlRaw("Exec GetApplicationCategory {0}", applicationID).ToListAsync();
if (category.Any())
{
return category.First();
}
}
return null;
}
When I want to retrieve an application then the field Category is not set. When I did not use async/await it would set the category automatically for me. Of course I could just return the Category Object from the GetCategoryByApplicationId and then say:
Application.Category = RetrievedFromDbCategory;
But this seems a bit unmaintainable compared to the previous behaviour. Why does this happen now and can I do something about it? Otherwise I don't see much benefits on using async/await .

Can I change my response data in OutputFormatter in ASP.NET Core 3.1

I'm trying to create a simple feature to make the first action act like the second one.
public IActionResult GetMessage()
{
return "message";
}
public IActionResult GetMessageDataModel()
{
return new MessageDataModel("message");
}
First idea came to my mind was to extend SystemTextJsonOutputFormater, and wrap context.Object with my data model in WriteResponseBodyAsync, but the action is marked sealed.
Then I tried to override WriteAsync but context.Object doesn't have protected setter, either.
Is there anyway I can achieve this by manipulating OutputFormatter?
Or I have another option instead of a custom OutputFormatter?
for some reason they prefer every response in a same format like {"return":"some message I write.","code":1}, hence I want this feature to achieve this instead of creating MessageDataModel every time.
Based on your description and requirement, it seems that you'd like to generate unified-format data globally instead of achieving it in each action's code logic. To achieve it, you can try to implement it in action filter, like below.
public class MyCustomFilter : Attribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
// implement code logic here
// based on your actual scenario
// get original message
// generate new instance of MessageDataModel
//example:
var mes = context.Result as JsonResult;
var model = new MessageDataModel
{
Code = 1,
Return = mes.Value.ToString()
};
context.Result = new JsonResult(model);
}
Apply it on specific action(s)
[MyCustomFilter]
public IActionResult GetMessage()
{
return Json("message");
}

Asp.net core Custom routing

I am trying to implement custom routing on an asp.net core application.
The desired result is the following:
http://Site_URL/MyController/Action/{Entity_SEO_Name}/
Entity_SEO_Name parameter will be a unique value saved into the database that it is going to help me identify the id of the entity that I am trying to display.
In order to achieve that I have implemented a custom route:
routes.MapMyCustomRoute(
name: "DoctorDetails",
template: " {controller=MyController}/{action=TestRoute}/{name?}");
public class MyTemplateRoute : TemplateRoute
{
public override async Task RouteAsync(RouteContext context)
{
//context.RouteData.Values are always empty. Here is the problem.
var seo_name = context.RouteData.Values["Entity_SEO_Name"];
int entityId = 0;
if (seo_name != null)
{
entityId = GetEntityIdFromDB(seo_name);
}
//Here i need to have the id and pass it to controller
context.RouteData.Values["id"] = entityId;
await base.RouteAsync(context);
}
}
My controller actionresult:
public ActionResult TestRoute(int id)
{
var entity = GetEntityById(id);
return Content("");
}
The problem with this approach is that the context.RouteData.Values are always empty.
Any ideas on how to move forward with this one ?
Your solution too complicated. You can have route template like
template: "{controller=Home}/{action=Index}/{seo?}"
and controller action just like
public ActionResult TestRoute(string seo)
{
var entity = GetEntityBySeo(seo);
return Content("");
}
It is enough, asp.net mvc is smart enough to bind seo variable to the parameter from url path.

FluentSecurity 2.0 support for action with parameters

In my .net mvc 4 app I am using the latest release of FluentSecurity (1.4) in order to secure my actions.
Here is an example that illustrates my problem:
Suppose I have a controller with 2 edit actions (get and post):
public class MyController : Controller
{
//
// GET: /My/
public ActionResult Edit(decimal id)
{
var modelToReturn = GetFromDb(id);
return View(modelToReturn);
}
[HttpPost]
public ActionResult Edit(MyModel model)
{
Service.saveToDb(model);
return View(model);
}
}
Now, I would like to have a different security policy for each action. To do that I define (using fluent security):
configuration.For<MyController>(x => x.Edit(0))
.AddPolicy(new MyPolicy("my.VIEW.permission"));
configuration.For<MyController>(x => x.Edit(null))
.AddPolicy(new MyPolicy("my.EDIT.permission"));
The first configuration refers to the get while the second to the post.
If you wonder why I'm sending dummy params you can have a look here and here.
Problem is that fluent security can't tell the difference between those 2, hence this doesn't work.
Couldn't find a way to overcome it (I'm open for ideas) and I wonder if installing the new 2.0 beta release can resolve this issue.
Any ideas?
It is currently not possible to apply different policies to each signature in FluentSecurity. This is because FluentSecurity can not know what signature will be called by ASP.NET MVC. All it knows is the name of the action. So FluentSecurity has to treat both action signatures as a single action.
However, you can apply multiple policies to the same action (you are not limited to have a single policy per action). With this, you can apply an Http verb filter for each of the policies. Below is an example of what it could look like:
1) Create a base policy you can inherit from
public abstract class HttpVerbFilteredPolicy : ISecurityPolicy
{
private readonly List<HttpVerbs> _httpVerbs;
protected HttpVerbFilteredPolicy(params HttpVerbs[] httpVerbs)
{
_httpVerbs = httpVerbs.ToList();
}
public PolicyResult Enforce(ISecurityContext securityContext)
{
HttpVerbs httpVerb;
Enum.TryParse(securityContext.Data.HttpVerb, true, out httpVerb);
return !_httpVerbs.Contains(httpVerb)
? PolicyResult.CreateSuccessResult(this)
: EnforcePolicy(securityContext);
}
protected abstract PolicyResult EnforcePolicy(ISecurityContext securityContext);
}
2) Create your custom policy
public class CustomPolicy : HttpVerbFilteredPolicy
{
private readonly string _role;
public CustomPolicy(string role, params HttpVerbs[] httpVerbs) : base(httpVerbs)
{
_role = role;
}
protected override PolicyResult EnforcePolicy(ISecurityContext securityContext)
{
var accessAllowed = //... Do your checks here;
return accessAllowed
? PolicyResult.CreateSuccessResult(this)
: PolicyResult.CreateFailureResult(this, "Access denied");
}
}
3) Add the HTTP verb of the current request to the Data property of ISecurityContext and secure your actions
SecurityConfigurator.Configure(configuration =>
{
// General setup goes here...
configuration.For<MyController>(x => x.Edit(0)).AddPolicy(new CustomPolicy("my.VIEW.permission", HttpVerbs.Get));
configuration.For<MyController>(x => x.Edit(null)).AddPolicy(new CustomPolicy("my.EDIT.permission", HttpVerbs.Post));
configuration.Advanced.ModifySecurityContext(context => context.Data.HttpVerb = HttpContext.Current.Request.HttpMethod);
});

Custom OpenIdClient for Customer URL in MVC 4

I'm working with the default template for MVC 4 and trying to add my own openID provider for example http://steamcommunity.com/dev to the list of openID logins and an openID box where the user can type in their openID information.
To add Google I just un-comment
OAuthWebSecurity.RegisterGoogleClient();
as for other custom solutions you can do something like
OAuthWebSecurity.RegisterClient(new SteamClient(),"Steam",null);
The trouble I have is creating SteamClient (or a generic one) http://blogs.msdn.com/b/webdev/archive/2012/08/23/plugging-custom-oauth-openid-providers.aspx doesn't show anywhere to change the URL.
I think the reason I could not find the answer is that most people thought it was common sense. I prefer my sense to be uncommon.
public class OidCustomClient : OpenIdClient
{
public OidCustomClient() : base("Oid", "http://localhost:5004/") { }
}
Based on #Jeff's answer I created a class to handle Stack Exchange OpenID.
Register:
OAuthWebSecurity.RegisterClient(new StackExchangeOpenID());
Class:
public class StackExchangeOpenID : OpenIdClient
{
public StackExchangeOpenID()
: base("stackexchange", "https://openid.stackexchange.com")
{
}
protected override Dictionary<string, string> GetExtraData(IAuthenticationResponse response)
{
FetchResponse fetchResponse = response.GetExtension<FetchResponse>();
if (fetchResponse != null)
{
var extraData = new Dictionary<string, string>();
extraData.Add("email", fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email));
extraData.Add("name", fetchResponse.GetAttributeValue(WellKnownAttributes.Name.FullName));
return extraData;
}
return null;
}
protected override void OnBeforeSendingAuthenticationRequest(IAuthenticationRequest request)
{
var fetchRequest = new FetchRequest();
fetchRequest.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
fetchRequest.Attributes.AddRequired(WellKnownAttributes.Name.FullName);
request.AddExtension(fetchRequest);
}
}
Retrieving extra data:
var result = OAuthWebSecurity.VerifyAuthentication();
result.ExtraData["email"];
result.ExtraData["name"];