asp.net mvc框架_实体框架DbGeography空间类型的ASP.NET MVC DisplayTemplate和EditorTemplates

asp.net mvc框架

asp.net mvc框架

UPDATE: Be sure to read the whole post including the refactoring update at the end via a Pull Request from Dave Ward.

更新:请确保通过Dave Ward的Pull Request阅读整个文章,包括最后的重构更新

I was trying to write a blog post about something totally different and was using this small EntityFramework 5 Code First model:

我试图写一篇关于完全不同的文章的博客,并且正在使用这个小的EntityFramework 5 Code First模型:

public class TouristAttraction
{
public int TouristAttractionId { get; set; }
public string Name { get; set; }

public DbGeography Location { get; set; }
}
The little bit I added

You'll notice I'm using the DbGeography spatial type with Latitude and Longitude. This support is new in Entity Framework 5. For a more complex example I might want to make a custom type of my own or split things up into a double lat and double long, but for this little app I just wanted a few fields and a map.

您会注意到我正在将DbGeography空间类型与纬度和经度一起使用。 此支持是Entity Framework 5中的新增功能。对于一个更复杂的示例,我可能想自己创建一个自定义类型或将其拆分为double lat和double long,但是对于这个小应用程序,我只需要一些字段和一个地图。

However, when I was scaffolding things out, of course, DbGeography was left in the cold. It wasn't scaffolded or included. When I added a map, there was no model binder. When I started making more complex views, there was no EditorFor or DisplayFor template support. So, rather than finishing the thing I was trying to do (I'll finish that project later) I became slightly obsessed focused with getting some kind of basic system working for DbGeography.

但是,当我脚手架放东西时,DbGeography当然是冷落的。 它不是脚手架或包括在内。 当我添加地图时,没有模型活页夹。 当我开始制作更复杂的视图时,不支持EditorFor或DisplayFor模板。 因此,我没有完成我想做的事情(我将在以后完成该项目),而是变得有点 着迷于 专注于为DbGeography工作的某种基本系统。

First, I scaffolded out a standard Entity Framework controller for "TouristAttraction" and changed nothing. The goal was to change nothing because DbGeography should just be treated like any other complex type. My prototype should be easily changeable for any other database or map system. There shouldn't be any Controller hacks to get it to work.

首先,我为“ TouristAttraction”搭建了一个标准的Entity Framework控制器,并没有进行任何更改。 目标是什么都不会改变,因为DbGeography应该像其他任何复杂类型一样被对待。 我的原型应该可以轻松更改为其他任何数据库或地图系统。 不应有任何Controller hack使其正常工作。

There's basically a DisplayTemplate and an EditorTemplate to show and edit maps. There's a model binder to handle DbGeography, and a small helper extension to get the client id for something more easily.

基本上有一个DisplayTemplate和一个EditorTemplate来显示和编辑地图。 有一个用于处理DbGeography的模型绑定程序,还有一个小的帮助程序扩展,可以更轻松地获取客户端ID。

There's also some supporting JavaScript that I'm sure Dave Ward will hate because I'm still learning how to write JavaScript the way the kids write it today. Refactoring is welcome.

我肯定还有一些支持JavaScript的Dave Ward会讨厌,因为我仍在学习如何像今天的孩子一样编写JavaScript。 欢迎重构。

创建和编辑 (Creating and Editing)

When creating a TouristAttraction you type the name then click the map. Clicking the map puts the lat,long in a text box (that could be hidden, of course).

创建TouristAttraction时,键入名称,然后单击地图。 单击地图可将经纬度放在一个文本框中(当然可以隐藏)。

Creating a location with a clickable Google Map

I simply added a Google Map to the Layout:

我只是将Google Map添加到布局中:

<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script> 

then on the Create.cshml used an EditorFor just like any other field:

然后在Create.cshml上使用EditorFor,就像其他任何字段一样:

<fieldset>
<legend>TouristAttraction</legend>

<div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Location)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Location)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>

There is an EditorTemplate called DbGeography, the name of the type by convention. If the type is "Foo" and the file is EditorTemplates/Foo.cshtml or DisplayTemplates/Foo.cshtml you can use EditorFor() and DisplayFor() and the whole app gets the benefits.

有一个称为DbGeography的EditorTemplate,根据惯例该类型的名称。 如果类型为“ Foo”,文件为EditorTemplates / Foo.cshtml或DisplayTemplates / Foo.cshtml,则可以使用EditorFor()和DisplayFor(),整个应用程序将从中受益。

@model System.Data.Spatial.DbGeography
@Html.TextBox("")
@if (Model != null) {
<script>
$(function () {
maps.markerToSet = new google.maps.LatLng(@Model.Latitude, @Model.Longitude);
});
</script>
}
@{ string textbox = Html.ClientIdFor(model => model).ToString(); }
<div id="map_canvas" data-textboxid="@textbox" style="width:400px; height:400px"></div>

This EditorTemplate needs to support Create as well as Edit so if Model isn't null it will set a variable that will be used later when the map is initialized in an Edit scenario.

这个EditorTemplate需要支持Create和Edit,因此如果Model不为null,它将设置一个变量,该变量将在以后在Edit场景中初始化地图时使用。

Next steps would be to make a generated map id for the div so that I could have multiple maps on one page. I'm close here as I'm sticking the "friend" textbox id in a data- attribute so I don't have to have any JavaScript on this page. All the JavaScript should figure out names unobtrusively. It's not there yet, but the base is there. I should also put the width and height elsewhere.

下一步将是为div生成一个生成的地图ID,这样我就可以在一页上包含多个地图。 我要在此处关闭,因为我将“朋友”文本框ID粘贴在data-属性中,因此此页面上不需要任何JavaScript。 所有JavaScript都应该毫不费力地找出名称。 它还没有,但是基地在那里。 我还应该将宽度和高度放在其他位置。

You'll notice the call to ClientIdFor. At this point I want the name of the textbox's client id but I don't know it. I don't want to make an EditorTemplate that only works hard-coded for one type so I need to get the generated value. I have an HtmlHelper extension method:

您会注意到对ClientIdFor的调用。 此时,我想要文本框的客户端ID的名称,但我不知道。 我不想制作一个只对一种类型进行硬编码的EditorTemplate,所以我需要获取生成的值。 我有一个HtmlHelper扩展方法:

public static partial class HtmlExtensions
{
public static MvcHtmlString ClientIdFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
return MvcHtmlString.Create(
htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(expression)));
}
}

This won't work unless you are sure to add your HtmlExtensions full namespace to the web.config in the Views folder under namespaces. That will let Razor see your HtmlHelper Extension method.

除非您确定将HtmlExtensions完整名称空间添加到名称空间下Views文件夹中web.config中,否则它将无法正常工作。 这将使Razor看到您的HtmlHelper扩展方法。

NOTE: Make sure you add a reference to System.Data.Entity to your MVC View's web.config in order to have a @model like I have above.

注意:确保将对System.Data.Entity的引用添加到MVC视图的web.config中,以便具有与上述相同的@model。

<compilation ... >
<assemblies>
<add assembly="System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
</assemblies>
</compilation>

When you click the map it fills out the text box. I've also got some initial values in here as well as the addListenerOnce which is used later in DisplayFor to load a read-only" map when viewing a record's details.

当您单击地图时,它将填写文本框。 在这里,我还有一些初始值,以及在稍后查看记录详细信息时在DisplayFor中用于加载只读地图的addListenerOnce。

function maps() { }
maps.mapInstance = null;
maps.marker= null;
maps.mapInstanceId = "map_canvas";
maps.markerToSet = null;

function initialize() {
var latlng = new google.maps.LatLng(40.716948, -74.003563); //a nice default
var options = {
zoom: 14, center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP,
maxZoom: 14 //so extents zoom doesn't go nuts
};
maps.mapInstance = new google.maps.Map(document.getElementById(maps.mapInstanceId), options);

google.maps.event.addListener(maps.mapInstance, 'click', function (event) {
placeMarker(event.latLng);
});

google.maps.event.addListenerOnce(maps.mapInstance, 'idle', function (event) {
if (maps.markerToSet) {
placeMarker(maps.markerToSet);
var bound = new google.maps.LatLngBounds();
bound.extend(maps.markerToSet);
maps.mapInstance.fitBounds(bound);
}
});
}

function placeMarker(location) {
if (maps.marker) {
maps.marker.setPosition(location);
} else {
maps.marker = new google.maps.Marker({
position: location,
map: maps.mapInstance
});
}

if (maps.marker) { //What's a better way than this dance?
var textboxid = $("#" + maps.mapInstanceId).data("textboxid");
$("#" + textboxid).val(maps.marker.getPosition().toUrlValue(13));
}
}

$(function () {
initialize();
});

When I've filled out a new location and hit SAVE the lat,long is POSTed to the controller which I want to "just work" so I made a Model Binder to handle the DbGeography type.

当我填写了一个新位置并单击“保存”后,long,long被发布到我想“正常工作”的控制器上,因此我制作了一个Model Binder以处理DbGeography类型。

I add it (actually its provider in case I add to it with other Entity Framework types) to the ModelBinderProviders collection in Global.asax:

我将其添加到Global.asax中的ModelBinderProviders集合中(实际上是它的提供程序,如果我将其与其他实体框架类型一起添加的话):

ModelBinderProviders.BinderProviders.Add(new EFModelBinderProvider());

The ModelBinder itself tries to be generic as well. I must say I see FAR too many CustomModelBinders out there with folks calling into Request.Form digging for custom strings. That's not reusable and it's just wrong.

ModelBinder本身也尝试通用。 我必须说,我看到FAR的CustomModelBinders太多了,人们呼唤Request.Form挖掘自定义字符串。 那是不可重用的,这是错误的。

public class DbGeographyModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
string[] latLongStr = valueProviderResult.AttemptedValue.Split(',');
string point = string.Format("POINT ({0} {1})",latLongStr[1], latLongStr[0]);
//4326 format puts LONGITUDE first then LATITUDE
DbGeography result = valueProviderResult == null ? null :
DbGeography.FromText(point,4326);
return result;
}
}

public class EFModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(Type modelType)
{
if (modelType == typeof(DbGeography))
{
return new DbGeographyModelBinder();
}
return null;
}
}

At this point, DbGeography is model bound and I don't have to change the controller. Now, again, I am NOT using a ViewModel here but I would on a larger application so be aware. I'd likely make a custom model binder for that hypothetical ViewModel and it would to be married to a database technology like this one is.

至此,DbGeography已绑定模型,我不必更改控制器。 现在,再次,我这里没有使用ViewModel,但是我会在更大的应用程序上使用,因此请注意。 我可能会为该假设的ViewModel制作一个自定义模型活页夹,并将它与这样的数据库技术结合起来。

You can see in the database that I've got the points stored as "geography" types and they are round tripping just fine.

您可以在数据库中看到,我已将这些点存储为“地理”类型,并且它们往返运行就很好了。

I threw this ASP.NET MVC 4 sample up on GitHub in https://github.com/shanselman/ASP.NET-MVC-and-DbGeography. You'll need VS2012 to play.

我将这个ASP.NET MVC 4示例放在https://github.com/shanselman/ASP.NET-MVC-and-DbGeography的GitHub上。 您需要VS2012才能玩。

Again, please forgive my 3am hacking and poor JavaScript but I hope you get the idea and find it useful. I think this has the potential to be packaged up into a NuGet or perhaps useful as an EntityFramework and ASP.NET MVC sample.

再次提醒您,请原谅我凌晨3点的黑客攻击和可怜JavaScript,但希望您能理解并有用。 我认为这有可能被打包到NuGet中,或者作为EntityFramework和ASP.NET MVC示例很有用。

IMPORTANT NOTE: In order to save lots of space space with samples I didn't check in binaries or include the packages folder with dependencies. Make sure that you have given NuGet permission to download missing packages during a build if you want to build this sample.

重要说明:为了节省样本的大量空间,我没有检入二进制文件或包含具有依赖项的packages文件夹。 如果要构建此样本,请确保已授予NuGet权限以在构建期间下载缺少的软件包。

Be sure to click "Allow NuGet to download missing packages during build"

I've enabled NuGet Package Restore in the project by right clicking on the Solution node and clicking "Enable NuGet Package Restore." This will cause NuGet to download the missing packages on build, so be aware as there will be a pause on the first build when NuGet updates the package.

我已经通过右键单击“解决方案”节点并单击“启用NuGet软件包还原”在项目中启用了NuGet软件包还原。 这将导致NuGet在构建中下载缺少的软件包,因此请注意,当NuGet更新软件包时,第一个构建将暂停。

更新1 (UPDATE #1)

My buddy Dave Ward took a look at the code and submitted a pull request that improves some of the JavaScript and makes it more the way "the kids are writing JavaScript these days."

我的好友Dave Ward看了一下代码,并提交了pull请求,该请求对JavaScript进行了一些改进,并使其更像“如今的孩子们在编写JavaScript”。

Dave has finished up a number of things for me. First, he's made things generic enough so one can have multiple maps on a page. He has also tidied up my JavaScript.

戴夫(Dave)为我完成了许多事情。 首先,他已经使事物具有足够的通用性,因此一个页面上可以有多个地图。 他还整理了我JavaScript。

He starts by removing any inline script from the DisplayTemplats and EditorTemplates and instead uses the TextBox itself to hold the data. This, of course, is just one of several "duh" moments for me and a reminder that using HTML elements to hold data is kind of what they're for. It also brings up an interesting debate about views and models. I did think about using KnockoutJS for this but thought it overkill.

他首先从DisplayTemplats和EditorTemplates中删除任何内联脚本,而是使用TextBox本身来保存数据。 对于我来说,这当然只是几个“ duh”时刻之一,并提醒我们使用HTML元素保存数据就是它们的用途。 它还引发了有关视图和模型的有趣辩论。 我确实考虑过为此使用KnockoutJS ,但认为这太过分了。

@model System.Data.Spatial.DbGeography
@if (Model != null) {
@Html.TextBox("", Model.Latitude + "," + Model.Longitude, new { @class = "editor-for-dbgeography" })
} else {
@Html.TextBox("", "", new { @class = "editor-for-dbgeography" })
}
Dave also introduces CSS classes as markers for the text boxes to identify them as editors for a DbGeography. That's editors, plural, since the primary limitation of my implementation was that there could be only one.

Dave now keys off the text boxes rather than a div. He initializes any of these text boxes (for display or editor) and dynamically adds the Google Map after each.

Dave现在关闭了文本框,而不是div。 他初始化了所有这些文本框(用于显示或编辑),并在每个文本框后动态添加Google Map。

$('.editor-for-dbgeography, .display-for-dbgeography').each(initialize);

He pulls the lat,long out of the text box and creates the map. When the text box changes he set a marker and pans the map to the new location.

他从文本框中拉出经纬度并创建地图。 文本框更改时,他设置了一个标记并将地图平移到新位置。

It's surprisingly readable.

令人惊讶的可读性。

(function() {
// Method signature matching $.fn.each()'s, for easy use in the .each loop later.
var initialize = function(i, el) {
// el is the input element that we need to initialize a map for, jQuery-ize it,
// and cache that since we'll be using it a few times.
var $input = $(el);

// Create the map div and insert it into the page.
var $map = $('<div>', {
css: {
width: '400px',
height: '400px'
}
}).insertAfter($input);

// Attempt to parse the lat/long coordinates out of this input element.
var latLong = parseLatLong(this.value);

// If there was a problem attaining a lat/long from the input element's value,
// set it to a sensible default that isn't in the middle of the ocean.
if (!latLong || !latLong.latitude || !latLong.longitude) {
latLong = {
latitude: 40.716948,
longitude: -74.003563
};
}

// Create a "Google(r)(tm)" LatLong object representing our DBGeometry's lat/long.
var position = new google.maps.LatLng(latLong.latitude, latLong.longitude);

// Initialize the map widget.
var map = new google.maps.Map($map[0], {
zoom: 14,
center: position,
mapTypeId: google.maps.MapTypeId.ROADMAP,
maxZoom: 14
});

// Place a marker on it, representing the DBGeometry object's position.
var marker = new google.maps.Marker({
position: position,
map: map
});

var updateMarker = function(updateEvent) {
marker.setPosition(updateEvent.latLng);

// This new location might be outside the current viewport, especially
// if it was manually entered. Pan to center on the new marker location.
map.panTo(updateEvent.latLng);

// Black magic, courtesy of Hanselman's original version.
$input.val(marker.getPosition().toUrlValue(13));
};

// If the input came from an EditorFor, initialize editing-related events.
if ($input.hasClass('editor-for-dbgeography')) {
google.maps.event.addListener(map, 'click', updateMarker);

// Attempt to react to user edits in the input field.
$input.on('change', function() {
var latLong = parseLatLong(this.value);

latLong = new google.maps.LatLng(latLong.latitude, latLong.longitude);

updateMarker({ latLng: latLong });
});
}
};

var parseLatLong = function(value) {
if (!value) { return undefined; }

var latLong = value.match(/-?\d+\.\d+/g);

return {
latitude: latLong[0],
longitude: latLong[1]
};
};

// Find all DBGeography inputs and initialize maps for them.
$('.editor-for-dbgeography, .display-for-dbgeography').each(initialize);
})();

The next step will be to bundle this all up in a NuGet. Any ideas on a clear name? AspNet.Net.Mvc.SpatialTemplates?

下一步是将所有内容捆绑到NuGet中。 有明确名称的想法吗? AspNet.Net.Mvc.SpatialTemplates?

翻译自: https://www.hanselman.com/blog/aspnet-mvc-displaytemplate-and-editortemplates-for-entity-framework-dbgeography-spatial-types

asp.net mvc框架

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值